From ad0aa6ccb3f1aa9aa96f8eeb75a183258d0bdc3e Mon Sep 17 00:00:00 2001 From: vanitynie Date: Fri, 4 Sep 2026 11:37:48 +0800 Subject: [PATCH] =?UTF-8?q?test(gfx):=20=E6=96=B0=E5=A2=9E=20custom-pipeli?= =?UTF-8?q?ne=20compute=20shader=20=E6=B5=8B=E8=AF=95=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E4=B8=8E=E8=87=AA=E5=8A=A8=E5=8C=96=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cases/GFX/compute-demo: compute-demo.scene + raytracingByCompute.ts(RaytracingWeekend 自定义管线,compute pass 写 storage image 后全屏 blit 输出) - resources/raytracing-compute: rt-compute(compute shader)+ rt-swizzle(输出)effect/material,保留 meta 以维持 mtl->effect uuid 引用 - auto-test-case/dynamic/GFX/compute-demo.test.ts: 截图式动态测试,import 管线模块确保 setCustomPipeline 注册 - 注意:3.8.4 运行需在项目设置将 Render Pipeline 切换为 custom-pipeline,浏览器预览后端 WebGPU;auto-test-config 的 sceneList 注册由本地 QA 自行维护 --- .../dynamic/GFX/compute-demo.test.ts | 19 + .../dynamic/GFX/compute-demo.test.ts.meta | 9 + assets/cases/GFX/compute-demo.meta | 9 + assets/cases/GFX/compute-demo/README.md | 45 +++ assets/cases/GFX/compute-demo/README.md.meta | 11 + .../cases/GFX/compute-demo/compute-demo.scene | 380 ++++++++++++++++++ .../GFX/compute-demo/compute-demo.scene.meta | 11 + .../GFX/compute-demo/raytracingByCompute.ts | 197 +++++++++ .../compute-demo/raytracingByCompute.ts.meta | 9 + assets/cases/GFX/pipeline-data.ts | 35 ++ assets/cases/GFX/pipeline-data.ts.meta | 9 + assets/resources/raytracing-compute.meta | 9 + .../raytracing-compute/rt-compute.effect | 136 +++++++ .../raytracing-compute/rt-compute.effect.meta | 11 + .../raytracing-compute/rt-compute.mtl | 29 ++ .../raytracing-compute/rt-compute.mtl.meta | 11 + .../raytracing-compute/rt-swizzle.effect | 47 +++ .../raytracing-compute/rt-swizzle.effect.meta | 11 + .../raytracing-compute/rt-swizzle.mtl | 32 ++ .../raytracing-compute/rt-swizzle.mtl.meta | 11 + 20 files changed, 1031 insertions(+) create mode 100644 assets/auto-test-case/dynamic/GFX/compute-demo.test.ts create mode 100644 assets/auto-test-case/dynamic/GFX/compute-demo.test.ts.meta create mode 100644 assets/cases/GFX/compute-demo.meta create mode 100644 assets/cases/GFX/compute-demo/README.md create mode 100644 assets/cases/GFX/compute-demo/README.md.meta create mode 100644 assets/cases/GFX/compute-demo/compute-demo.scene create mode 100644 assets/cases/GFX/compute-demo/compute-demo.scene.meta create mode 100644 assets/cases/GFX/compute-demo/raytracingByCompute.ts create mode 100644 assets/cases/GFX/compute-demo/raytracingByCompute.ts.meta create mode 100644 assets/cases/GFX/pipeline-data.ts create mode 100644 assets/cases/GFX/pipeline-data.ts.meta create mode 100644 assets/resources/raytracing-compute.meta create mode 100644 assets/resources/raytracing-compute/rt-compute.effect create mode 100644 assets/resources/raytracing-compute/rt-compute.effect.meta create mode 100644 assets/resources/raytracing-compute/rt-compute.mtl create mode 100644 assets/resources/raytracing-compute/rt-compute.mtl.meta create mode 100644 assets/resources/raytracing-compute/rt-swizzle.effect create mode 100644 assets/resources/raytracing-compute/rt-swizzle.effect.meta create mode 100644 assets/resources/raytracing-compute/rt-swizzle.mtl create mode 100644 assets/resources/raytracing-compute/rt-swizzle.mtl.meta diff --git a/assets/auto-test-case/dynamic/GFX/compute-demo.test.ts b/assets/auto-test-case/dynamic/GFX/compute-demo.test.ts new file mode 100644 index 000000000..7d54fa97e --- /dev/null +++ b/assets/auto-test-case/dynamic/GFX/compute-demo.test.ts @@ -0,0 +1,19 @@ +//@ts-ignore +import { director } from 'cc'; +//@ts-ignore +import { runScene, testCase, testClass } from 'db://automation-framework/runtime/test-framework.mjs'; +import { screenshot_custom } from '../common/utils'; +// ensure the compute pipeline module is loaded/registered before the scene renders +import '../../../cases/GFX/compute-demo/raytracingByCompute'; + +@runScene('compute-demo') +@testClass('ComputeDemo') +export class ComputeDemo { + // give the compute pipeline a few frames to create storage/dispatch resources + _delay = 5; + + @testCase + async index() { + await screenshot_custom(this._delay); + } +} diff --git a/assets/auto-test-case/dynamic/GFX/compute-demo.test.ts.meta b/assets/auto-test-case/dynamic/GFX/compute-demo.test.ts.meta new file mode 100644 index 000000000..47b1f97da --- /dev/null +++ b/assets/auto-test-case/dynamic/GFX/compute-demo.test.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "67fcc6d9-9d47-4afa-ad24-615700d5df98", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/cases/GFX/compute-demo.meta b/assets/cases/GFX/compute-demo.meta new file mode 100644 index 000000000..334b49137 --- /dev/null +++ b/assets/cases/GFX/compute-demo.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "1c1a1db3-316b-49b0-811a-054e2d66cd12", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/cases/GFX/compute-demo/README.md b/assets/cases/GFX/compute-demo/README.md new file mode 100644 index 000000000..6bf074221 --- /dev/null +++ b/assets/cases/GFX/compute-demo/README.md @@ -0,0 +1,45 @@ +# compute-demo (Custom Pipeline / Compute Shader) + +Mirror of Cocos official tutorial-005-compute (raytracing by compute) from +cocos-example-custom-pipeline, adapted as an automation test case. + +## 运行前提(重要) + +1. **后端(Backend)**:浏览器预览必须先开启 **WebGPU** —— 使用支持 WebGPU 的 + Chrome / Edge 等浏览器(较新版本默认开启;旧版本需在 chrome://flags 打开 + WebGPU 相关开关)。桌面预览可用 Vulkan / Metal。 +2. **渲染管线(图形设置)**:项目设置 -> 功能裁剪(Feature Cropping)-> 图形 / + 渲染管线选择**新的渲染管线(custom-pipeline)**,并在管线名称(Custom Pipeline + Name)中填写 **RaytracingWeekend** —— 必须与 raytracingByCompute.ts 中 + rendering.setCustomPipeline('RaytracingWeekend', ...) 的注册名一致,否则管线不会生效。 + +## 文件说明 + +- raytracingByCompute.ts - PipelineBuilder + rendering.setCustomPipeline('RaytracingWeekend', ...) + (compute pass writes to a storage texture, a fullscreen quad blits it to the swapchain) +- compute-demo.scene - camera + main light only, compute output fills the window +- ../pipeline-data.ts - window/framebuffer helper imported by the pipeline script +- assets/resources/raytracing-compute/ - rt-compute (compute shader) and + rt-swizzle (blit) effects + materials, loaded at runtime via resources.load + +## QA 步骤 + +1. 按"运行前提"完成配置:浏览器预览开启 WebGPU;图形设置选新渲染管线, + 管线名称填 RaytracingWeekend。 +2. 打开场景 compute-demo.scene。手动预览时,把 raytracingByCompute.ts + (组件 pipeline_005_raytracing_in_1_weekend)挂到任意节点上, + 否则管线不会被注册。 +3. 预览或跑自动化验证 compute 输出。 + +## 自动化 + +- Registered in assets/auto-test-config.json under sceneList as compute-demo. +- Test: assets/auto-test-case/dynamic/GFX/compute-demo.test.ts (screenshot based). + +## 维护 + +- Edit scene content in rt-compute.effect (spheres uniform, imageStore). +- addDispatch(width/8, height/4, 1) must match layout(local_size_x = 8, local_size_y = 4). +- The .effect.meta/.mtl.meta files are kept on purpose: .mtl binds the + effect by uuid. If you delete them, re-assign the effect on both materials + after Creator regenerates the metas. diff --git a/assets/cases/GFX/compute-demo/README.md.meta b/assets/cases/GFX/compute-demo/README.md.meta new file mode 100644 index 000000000..3c0f69c95 --- /dev/null +++ b/assets/cases/GFX/compute-demo/README.md.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.1", + "importer": "text", + "imported": true, + "uuid": "8bff7cee-33e4-4903-8f38-87f2e4c243ca", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/cases/GFX/compute-demo/compute-demo.scene b/assets/cases/GFX/compute-demo/compute-demo.scene new file mode 100644 index 000000000..130b1ea3b --- /dev/null +++ b/assets/cases/GFX/compute-demo/compute-demo.scene @@ -0,0 +1,380 @@ +[ + { + "__type__": "cc.SceneAsset", + "_name": "compute-demo", + "_objFlags": 0, + "__editorExtras__": {}, + "_native": "", + "scene": { + "__id__": 1 + } + }, + { + "__type__": "cc.Scene", + "_name": "compute-demo", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": null, + "_children": [ + { + "__id__": 2 + }, + { + "__id__": 5 + } + ], + "_active": true, + "_components": [], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "autoReleaseAssets": false, + "_globals": { + "__id__": 7 + }, + "_id": "2246ac00-2e5c-481d-aa5b-953387e98bcb" + }, + { + "__type__": "cc.Node", + "_name": "Main Light", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 3 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": -0.06397656665577071, + "y": -0.44608233363525845, + "z": -0.8239028751062036, + "w": -0.3436591377065261 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": -117.894, + "y": -194.909, + "z": 38.562 + }, + "_id": "c0y6F5f+pAvI805TdmxIjx" + }, + { + "__type__": "cc.DirectionalLight", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": null, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 250, + "b": 240, + "a": 255 + }, + "_useColorTemperature": false, + "_colorTemperature": 6550, + "_staticSettings": { + "__id__": 4 + }, + "_visibility": -325058561, + "_illuminanceHDR": 65000, + "_illuminance": 65000, + "_illuminanceLDR": 1.6927083333333335, + "_shadowEnabled": false, + "_shadowPcf": 0, + "_shadowBias": 0.00001, + "_shadowNormalBias": 0, + "_shadowSaturation": 1, + "_shadowDistance": 50, + "_shadowInvisibleOcclusionRange": 200, + "_csmLevel": 4, + "_csmLayerLambda": 0.75, + "_csmOptimizationMode": 2, + "_csmAdvancedOptions": false, + "_csmLayersTransition": false, + "_csmTransitionRange": 0.05, + "_shadowFixedArea": false, + "_shadowNear": 0.1, + "_shadowFar": 10, + "_shadowOrthoSize": 5, + "_id": "597uMYCbhEtJQc0ffJlcgA" + }, + { + "__type__": "cc.StaticLightSettings", + "_baked": false, + "_editorOnly": false, + "_castShadow": false + }, + { + "__type__": "cc.Node", + "_name": "Main Camera", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 6 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": -10, + "y": 10, + "z": 10 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": -0.27781593346944056, + "y": -0.36497167621709875, + "z": -0.11507512748638377, + "w": 0.8811195706053617 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": -35, + "y": -45, + "z": 0 + }, + "_id": "c9DMICJLFO5IeO07EPon7U" + }, + { + "__type__": "cc.Camera", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 5 + }, + "_enabled": true, + "__prefab": null, + "_projection": 1, + "_priority": 0, + "_fov": 45, + "_fovAxis": 0, + "_orthoHeight": 10, + "_near": 1, + "_far": 1000, + "_color": { + "__type__": "cc.Color", + "r": 51, + "g": 51, + "b": 51, + "a": 255 + }, + "_depth": 1, + "_stencil": 0, + "_clearFlags": 14, + "_rect": { + "__type__": "cc.Rect", + "x": 0, + "y": 0, + "width": 1, + "height": 1 + }, + "_aperture": 19, + "_shutter": 7, + "_iso": 0, + "_screenScale": 1, + "_visibility": 1822425087, + "_targetTexture": null, + "_postProcess": null, + "_usePostProcess": false, + "_cameraType": -1, + "_trackingType": 0, + "_id": "7dWQTpwS5LrIHnc1zAPUtf" + }, + { + "__type__": "cc.SceneGlobals", + "ambient": { + "__id__": 8 + }, + "shadows": { + "__id__": 9 + }, + "_skybox": { + "__id__": 10 + }, + "fog": { + "__id__": 11 + } + }, + { + "__type__": "cc.AmbientInfo", + "_skyColorHDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 0.520833125 + }, + "_skyColor": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 0.520833125 + }, + "_skyIllumHDR": 20000, + "_skyIllum": 20000, + "_groundAlbedoHDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + }, + "_groundAlbedo": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + }, + "_skyColorLDR": { + "__type__": "cc.Vec4", + "x": 0.452588, + "y": 0.607642, + "z": 0.755699, + "w": 0 + }, + "_skyIllumLDR": 0.8, + "_groundAlbedoLDR": { + "__type__": "cc.Vec4", + "x": 0.618555, + "y": 0.577848, + "z": 0.544564, + "w": 0 + } + }, + { + "__type__": "cc.ShadowsInfo", + "_enabled": false, + "_type": 0, + "_normal": { + "__type__": "cc.Vec3", + "x": 0, + "y": 1, + "z": 0 + }, + "_distance": 0, + "_shadowColor": { + "__type__": "cc.Color", + "r": 76, + "g": 76, + "b": 76, + "a": 255 + }, + "_maxReceived": 4, + "_size": { + "__type__": "cc.Vec2", + "x": 1024, + "y": 1024 + } + }, + { + "__type__": "cc.SkyboxInfo", + "_envLightingType": 0, + "_envmapHDR": { + "__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0", + "__expectedType__": "cc.TextureCube" + }, + "_envmap": { + "__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0", + "__expectedType__": "cc.TextureCube" + }, + "_envmapLDR": { + "__uuid__": "6f01cf7f-81bf-4a7e-bd5d-0afc19696480@b47c0", + "__expectedType__": "cc.TextureCube" + }, + "_diffuseMapHDR": null, + "_diffuseMapLDR": null, + "_enabled": true, + "_useHDR": true, + "_editableMaterial": null, + "_reflectionHDR": null, + "_reflectionLDR": null, + "_rotationAngle": 0 + }, + { + "__type__": "cc.FogInfo", + "_type": 0, + "_fogColor": { + "__type__": "cc.Color", + "r": 200, + "g": 200, + "b": 200, + "a": 255 + }, + "_enabled": false, + "_fogDensity": 0.3, + "_fogStart": 0.5, + "_fogEnd": 300, + "_fogAtten": 5, + "_fogTop": 1.5, + "_fogRange": 1.2, + "_accurate": false + } +] \ No newline at end of file diff --git a/assets/cases/GFX/compute-demo/compute-demo.scene.meta b/assets/cases/GFX/compute-demo/compute-demo.scene.meta new file mode 100644 index 000000000..8eb4053e7 --- /dev/null +++ b/assets/cases/GFX/compute-demo/compute-demo.scene.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.1.50", + "importer": "scene", + "imported": true, + "uuid": "2246ac00-2e5c-481d-aa5b-953387e98bcb", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/cases/GFX/compute-demo/raytracingByCompute.ts b/assets/cases/GFX/compute-demo/raytracingByCompute.ts new file mode 100644 index 000000000..52cf3362b --- /dev/null +++ b/assets/cases/GFX/compute-demo/raytracingByCompute.ts @@ -0,0 +1,197 @@ +import { + _decorator, + Component, + gfx, + Material, + renderer, + rendering, + resources, + Vec4, +} from 'cc'; +import { getWindowInfo, WindowInfo } from '../pipeline-data'; +const { ccclass, property } = _decorator; + +import { JSB } from 'cc/env'; + +function addOrUpdateRenderTarget(name: string, format: gfx.Format, width: number, height: number, residency: rendering.ResourceResidency, pipeline: rendering.Pipeline) { + if (!pipeline.containsResource(name)) { + pipeline.addRenderTarget(name, format, width, height, residency); + } else { + pipeline.updateRenderTarget(name, width, height); + } +} + +function addOrUpdateStorageTexture(name: string, format: gfx.Format, width: number, height: number, residency: rendering.ResourceResidency, pipeline: rendering.Pipeline) { + if (!pipeline.containsResource(name)) { + pipeline.addStorageTexture(name, format, width, height, residency); + } else { + pipeline.updateStorageTexture(name, width, height, format); + } +} + +function addOrUpdateStorageBuffer(name: string, format: gfx.Format, size: number, residency: rendering.ResourceResidency, pipeline: rendering.Pipeline) { + if (!pipeline.containsResource(name)) { + pipeline.addStorageBuffer(name, format, size, residency); + } else { + pipeline.updateStorageBuffer(name, size); + } +} + +function addOrUpdateUniformBuffer(name: string, flags: rendering.ResourceFlags, size: number, residency: rendering.ResourceResidency, pipeline: rendering.Pipeline) { + if (!pipeline.containsResource(name)) { + pipeline.addBuffer(name, size, flags, residency); + } else { + pipeline.updateBuffer(name, size); + } +} + +const matArr: { name: string, path: string }[] = [ + { name: 'rt1w', path: 'raytracing-compute/rt-compute' }, + { name: 'swizzleQuad', path: 'raytracing-compute/rt-swizzle' }, +]; + +const materialMap = new Map(); +function loadResource() { + for (let pair of matArr) { + resources.load(pair.path, Material, (error, material) => { + materialMap.set(pair.name, material); + }); + } +} + +// implement a custom pipeline +// 如何实现一个自定义渲染管线 +class RaytracingByComputePipeline implements rendering.PipelineBuilder { + // @property(CameraComponent) + // mainCam: CameraComponent = null!; + + + // implemenation of rendering.PipelineBuilder interface + // 实现 rendering.PipelineBuilder 接口 + public setup(cameras: renderer.scene.Camera[], ppl: rendering.BasicPipeline): void { + for (let i = 0; i < cameras.length; i++) { + const camera = cameras[i]; + // skip invalid camera + // 跳过无效的摄像机 + if (camera.scene === null || camera.window === null) { + continue; + } + // prepare camera resources + // 准备摄像机资源 + const info = this.prepareCameraResources(ppl, camera); + + // build forward lighting for editor or game view + // 为编辑器以及游戏视图构建前向光照管线 + this.buildForward(ppl, camera, info.id, info.width, info.height); + } + } + // internal methods + // 管线内部方法 + private prepareCameraResources( + ppl: rendering.BasicPipeline, + camera: renderer.scene.Camera): WindowInfo { + // get window info + // 获取窗口信息 + const info = getWindowInfo(camera); + // check if camera resources are initialized + // 检查摄像机资源是否已经初始化 + if (info.width === 0 && info.height === 0) { + info.width = camera.window.width; + info.height = camera.window.height; + this.initCameraResources(ppl, camera, info.id, info.width, info.height); + } else if ( + // we need to check framebuffer because window may be reused + // 我们需要检查 framebuffer,因为窗口可能被重用 + info.framebuffer !== camera.window.framebuffer || + // we need to check width and height because window may be resized + // 我们需要检查宽度和高度,因为窗口可能被调整大小 + info.width !== camera.window.width || + info.height !== camera.window.height) { + // resized or invalidated + // 调整大小或者失效 + info.framebuffer = camera.window.framebuffer; // update framebuffer + info.width = camera.window.width; // update width + info.height = camera.window.height; // update height + // update resources + // 更新资源 + this.initCameraResources(ppl, camera, info.id, info.width, info.height); + } + // return window info + // 返回窗口信息 + return info; + } + + private initCameraResources(ppl: rendering.BasicPipeline, camera: renderer.scene.Camera, id: number, width: number, height: number): void { + // all resource can be initialized here + // 所有资源可以在这里初始化 + ppl.addRenderWindow(`Color${id}`, gfx.Format.BGRA8, width, height, camera.window); + ppl.addDepthStencil(`DepthStencil${id}`, gfx.Format.DEPTH_STENCIL, width, height); + } + + // build a subpass test pipeline; usage shown below + // 构建前一个subpass测例 + private buildForward( + ppl: rendering.BasicPipeline, + camera: renderer.scene.Camera, + id: number, // window id + width: number, + height: number): void { + + + const pipeline = ppl as rendering.Pipeline; + addOrUpdateStorageTexture("storage", gfx.Format.RGBA8, width, height, rendering.ResourceResidency.MANAGED, pipeline); + addOrUpdateUniformBuffer("spheres", 2 * 4 * 4, rendering.ResourceFlags.UNIFORM, rendering.ResourceResidency.MANAGED, pipeline); + + const spheres: Float32Array = new Float32Array([ + 0, 0, -1, 0.5, + 0, -100.5, -1, 100, + ]); + + if (!this._buffer) { + this._buffer = gfx.deviceManager.gfxDevice.createBuffer(new gfx.BufferInfo( + gfx.BufferUsageBit.UNIFORM, + gfx.MemoryUsageBit.DEVICE, + 2 * 4 * 4, + 4 * 4) + ); + this._buffer.update(spheres.buffer); + } + + // compute + const computePass = pipeline.addComputePass("rt1w"); + computePass.addStorageImage("storage", rendering.AccessType.WRITE, "co"); + computePass.setBuffer("b_sphereBuffer", this._buffer); + computePass.addQueue().addDispatch(width / 8, height / 4, 1, materialMap.get("rt1w")); + + // sample storage texture to swapchain + const renderTarget = `Color${id}`; + const pass = pipeline.addRenderPass(width, height, "swizzle"); + pass.addRenderTarget(renderTarget, gfx.LoadOp.DISCARD, gfx.StoreOp.STORE) + pass.addTexture("storage", "mainTexture"); + pass.addQueue(rendering.QueueHint.OPAQUE, "swizzle-phase") + .addFullscreenQuad(materialMap.get("swizzleQuad"), 0); + + } + + private _buffer: gfx.Buffer | null = null; +} + +// register pipeline +// 注册管线 +if (rendering) { + rendering.setCustomPipeline('RaytracingWeekend', new RaytracingByComputePipeline()); +} + +loadResource(); + +@ccclass('pipeline_005_raytracing_in_1_weekend') +export class pipeline_005_raytracing_in_1_weekend extends Component { + start() { + // noop + // 空操作 + } + update(deltaTime: number) { + // noop + // 空操作 + } +} diff --git a/assets/cases/GFX/compute-demo/raytracingByCompute.ts.meta b/assets/cases/GFX/compute-demo/raytracingByCompute.ts.meta new file mode 100644 index 000000000..20a4ff1af --- /dev/null +++ b/assets/cases/GFX/compute-demo/raytracingByCompute.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "59f2bd8a-d0b8-4e65-b6b5-506927ffddb2", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/cases/GFX/pipeline-data.ts b/assets/cases/GFX/pipeline-data.ts new file mode 100644 index 000000000..9b0a351d7 --- /dev/null +++ b/assets/cases/GFX/pipeline-data.ts @@ -0,0 +1,35 @@ +import { cclegacy, renderer, gfx } from "cc"; + +export class WindowInfo { + constructor (id: number, width: number, height: number, framebuffer: gfx.Framebuffer) { + this.id = id; + this.width = width; + this.height = height; + this.framebuffer = framebuffer; + } + id = 0xFFFFFFFF; + width = 0; + height = 0; + framebuffer: gfx.Framebuffer; +} + +if (cclegacy.user === undefined) { + cclegacy.user = new Object(); + cclegacy.user.windowID = 0; + cclegacy.user.windows = new WeakMap(); +} + +export function getWindowInfo(camera: renderer.scene.Camera): WindowInfo { + let info = cclegacy.user.windows.get(camera.window); + if (info !== undefined) { + return info; + } + info = new WindowInfo(cclegacy.user.windowID, 0, 0, camera.window.framebuffer); + ++cclegacy.user.windowID; + cclegacy.user.windows.set(camera.window, info); + return info; +} + +export function needClearColor(camera: renderer.scene.Camera): boolean { + return !!(camera.clearFlag & (gfx.ClearFlagBit.COLOR | (gfx.ClearFlagBit.STENCIL << 1))); +} diff --git a/assets/cases/GFX/pipeline-data.ts.meta b/assets/cases/GFX/pipeline-data.ts.meta new file mode 100644 index 000000000..fafee7511 --- /dev/null +++ b/assets/cases/GFX/pipeline-data.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "74d211c4-8a70-4111-9241-1b3a1b0b4314", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/resources/raytracing-compute.meta b/assets/resources/raytracing-compute.meta new file mode 100644 index 000000000..5aa8c46b8 --- /dev/null +++ b/assets/resources/raytracing-compute.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "984aa0c2-a574-424a-b62b-92d66ac246f5", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/resources/raytracing-compute/rt-compute.effect b/assets/resources/raytracing-compute/rt-compute.effect new file mode 100644 index 000000000..3f9fcd587 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-compute.effect @@ -0,0 +1,136 @@ +// Effect Syntax Guide: https://docs.cocos.com/creator/manual/zh/shader/index.html + +CCEffect %{ + techniques: + - name: opaque + passes: + - pass: rt1w + compute: rt-comp +}% + +CCProgram rt-comp %{ + precision highp float; + precision mediump image2D; + + const uint OBJ_NUM = 2; + const float MAX_DISTANCE = 100000000.0F; + + layout(local_size_x = 8, local_size_y = 4, local_size_z = 1) in; + + // xyz - vec3 center, w - radius + #pragma rate b_sphereBuffer pass + layout(std140) uniform b_sphereBuffer { + vec4 b_spheres[2]; + }; + + #pragma rate co pass + layout (rgba8) writeonly uniform image2D co; + + struct Ray { + vec3 origin; + vec3 direction; + }; + + struct Sphere { + vec3 center; + float radius; + }; + + struct HitRecord { + vec3 p; + vec3 normal; + float t; + bool frontFace; + }; + + float hit_sphere(vec3 center, float radius, Ray r) { + vec3 oc = r.origin - center; + float a = dot(r.direction, r.direction); + float half_b = dot(oc, r.direction); + float c = dot(oc, oc) - radius*radius; + float discriminant = half_b*half_b - a*c; + return discriminant < 0.F ? -1.F : -half_b - sqrt(discriminant); + } + + void setFaceNormal(in Ray r, in vec3 outNormal, inout HitRecord record) { + record.frontFace = dot(r.direction, outNormal) < 0; + record.normal = record.frontFace ? outNormal : -outNormal; + } + + bool hit(in Sphere s, in Ray r, float t_min, float t_max, inout HitRecord record) { + vec3 oc = r.origin - s.center; + float a = dot(r.direction, r.direction); + float half_b = dot(oc, r.direction); + float c = dot(oc, oc) - s.radius*s.radius; + float discriminant = half_b*half_b - a*c; + if (discriminant > 0) { + float root = sqrt(discriminant); + + // front + float temp = (-half_b - root) / a; + if(temp < t_max && temp > t_min) { + record.t = temp; + record.p = r.origin + temp * r.direction; + vec3 outNormal = (record.p - s.center) / s.radius; + setFaceNormal(r, outNormal, record); + return true; + } + + // back + temp = (-half_b + root) / a; + if(temp < t_max && temp > t_min) { + record.t = temp; + record.p = r.origin + temp * r.direction; + vec3 outNormal = (record.p - s.center) / s.radius; + setFaceNormal(r, outNormal, record); + return true; + } + } + return false; + } + + bool hit_object(in Ray r, float t_min, float t_max, inout HitRecord record) { + HitRecord rec; + bool hitCheck = false; + float closest = t_max; + for (uint i = 0; i < OBJ_NUM; i++) { + vec4 v = b_spheres[i]; + Sphere s; + s.center = v.xyz; + s.radius = v.w; + if(hit(s, r, t_min, closest, rec)) { + hitCheck = true; + closest = record.t; + record = rec; + } + } + return hitCheck; + } + + vec4 ray_color(in Ray r) { + HitRecord record; + if(hit_object(r, 0, MAX_DISTANCE, record)) { + return vec4(0.5 * (record.normal + vec3(1.0, 1.0, 1.0)), 1.0); + } + + ivec2 size = imageSize(co); + uint currY = gl_GlobalInvocationID.y; + float ratio = float(currY) / float(size.y); + vec3 res = (1.0 - ratio) * vec3(1.0, 1.0, 1.0) + ratio * vec3(0.5, 0.7, 1.0); + return vec4(res, 1.0); + } + + void main () { + Ray r; + r.origin = vec3(0.0, 0.0, 0.0); + + ivec2 size = imageSize(co); + float aspect = float(size.x) / float(size.y); + vec2 ndc = vec2(float(gl_GlobalInvocationID.x) / float(size.x) * 2.0 - 1.0, float(gl_GlobalInvocationID.y) / float(size.y) * 2.0 - 1.0); + ndc.x *= aspect; + r.direction = normalize(vec3(ndc, -1.0)); + + vec4 color = ray_color(r); + imageStore(co, ivec2(gl_GlobalInvocationID.xy), color); + } +}% \ No newline at end of file diff --git a/assets/resources/raytracing-compute/rt-compute.effect.meta b/assets/resources/raytracing-compute/rt-compute.effect.meta new file mode 100644 index 000000000..da0883030 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-compute.effect.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.7.1", + "importer": "effect", + "imported": true, + "uuid": "347c91aa-f987-4aa4-a61d-816c526a12d9", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/resources/raytracing-compute/rt-compute.mtl b/assets/resources/raytracing-compute/rt-compute.mtl new file mode 100644 index 000000000..6079eac45 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-compute.mtl @@ -0,0 +1,29 @@ +{ + "__type__": "cc.Material", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "_native": "", + "_effectAsset": { + "__uuid__": "347c91aa-f987-4aa4-a61d-816c526a12d9", + "__expectedType__": "cc.EffectAsset" + }, + "_techIdx": 0, + "_defines": [ + {} + ], + "_states": [ + { + "rasterizerState": {}, + "depthStencilState": {}, + "blendState": { + "targets": [ + {} + ] + } + } + ], + "_props": [ + {} + ] +} \ No newline at end of file diff --git a/assets/resources/raytracing-compute/rt-compute.mtl.meta b/assets/resources/raytracing-compute/rt-compute.mtl.meta new file mode 100644 index 000000000..5995a20e7 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-compute.mtl.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.21", + "importer": "material", + "imported": true, + "uuid": "ce7c1c65-0f31-44c1-b0ad-78f60b4c9dcd", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/resources/raytracing-compute/rt-swizzle.effect b/assets/resources/raytracing-compute/rt-swizzle.effect new file mode 100644 index 000000000..517cdfbea --- /dev/null +++ b/assets/resources/raytracing-compute/rt-swizzle.effect @@ -0,0 +1,47 @@ +// Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +CCEffect %{ + techniques: + - passes: + - vert: swizzle-pass-vs + frag: swizzle-pass-fs + pass: swizzle + phase: swizzle-phase + depthStencilState: + depthTest: false + depthWrite: false + rasterizerState: + cullMode: none +}% + +CCProgram swizzle-pass-vs %{ + #include + #include + precision highp float; + out vec2 v_uv; + + void main () { + vec4 pos = vec4(a_position, 1.0); + gl_Position = pos; + v_uv = a_texCoord; + } +}% + +CCProgram swizzle-pass-fs %{ + precision highp float; + in vec2 v_uv; + + #pragma rate mainTexture pass + layout(binding = 0) uniform sampler2D mainTexture; + + layout(location = 0) out vec4 fragColor; + +void main () { + #if defined(CC_USE_METAL) || defined(CC_USE_WGPU) + vec2 uv = vec2(v_uv.x, 1.0 - v_uv.y); + #else + vec2 uv = v_uv; + #endif + fragColor = texture(mainTexture, uv); + } +}% diff --git a/assets/resources/raytracing-compute/rt-swizzle.effect.meta b/assets/resources/raytracing-compute/rt-swizzle.effect.meta new file mode 100644 index 000000000..c1db4e1a5 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-swizzle.effect.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.7.1", + "importer": "effect", + "imported": true, + "uuid": "ecf3a0db-f2a7-4c73-9dc0-45398ce6a3ba", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/resources/raytracing-compute/rt-swizzle.mtl b/assets/resources/raytracing-compute/rt-swizzle.mtl new file mode 100644 index 000000000..32e784be3 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-swizzle.mtl @@ -0,0 +1,32 @@ +{ + "__type__": "cc.Material", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "_native": "", + "_effectAsset": { + "__uuid__": "ecf3a0db-f2a7-4c73-9dc0-45398ce6a3ba", + "__expectedType__": "cc.EffectAsset" + }, + "_techIdx": 0, + "_defines": [ + {} + ], + "_states": [ + { + "rasterizerState": {}, + "depthStencilState": { + "depthTest": false, + "depthWrite": false + }, + "blendState": { + "targets": [ + {} + ] + } + } + ], + "_props": [ + {} + ] +} \ No newline at end of file diff --git a/assets/resources/raytracing-compute/rt-swizzle.mtl.meta b/assets/resources/raytracing-compute/rt-swizzle.mtl.meta new file mode 100644 index 000000000..4027ca747 --- /dev/null +++ b/assets/resources/raytracing-compute/rt-swizzle.mtl.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.21", + "importer": "material", + "imported": true, + "uuid": "8667b558-c772-45a8-9a35-bbf91f534cf2", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +}