diff --git a/src/common/extent.hpp b/src/common/extent.hpp index a36b6a5..7796a2f 100644 --- a/src/common/extent.hpp +++ b/src/common/extent.hpp @@ -41,7 +41,7 @@ constexpr float aspectRatio(const Extent2& extent) noexcept template constexpr T area(const Extent2& extent) noexcept { - return static_cast(extent.x) * static_cast(extent.y); + return static_cast(extent.x) * static_cast(extent.y); } template diff --git a/src/pt/deferred_renderer.cpp b/src/pt/deferred_renderer.cpp index 7c2c970..d17d324 100644 --- a/src/pt/deferred_renderer.cpp +++ b/src/pt/deferred_renderer.cpp @@ -1342,11 +1342,6 @@ DeferredRenderer::LightingPass::LightingPass( std::span sceneVertexAttributes, std::span sceneBaseColorTextures) : mCurrentSky{}, - mVertexBuffer{ - gpuContext.device, - "Sky vertex buffer", - {GpuBufferUsage::Vertex, GpuBufferUsage::CopyDst}, - std::span(quadVertexData)}, mSkyStateBuffer{ gpuContext.device, "Sky state buffer", @@ -1526,39 +1521,6 @@ DeferredRenderer::LightingPass::LightingPass( { // Pipeline layout - const WGPUBindGroupLayout bindGroupLayouts[] = { - skyStateBindGroupLayout.ptr(), - uniformBindGroupLayout.ptr(), - gbufferBindGroupLayout.ptr(), - bvhBindGroupLayout.ptr()}; - - const WGPUPipelineLayoutDescriptor pipelineLayoutDesc{ - .nextInChain = nullptr, - .label = "Lighting pass pipeline layout", - .bindGroupLayoutCount = std::size(bindGroupLayouts), - .bindGroupLayouts = bindGroupLayouts, - }; - - const WGPUPipelineLayout pipelineLayout = - wgpuDeviceCreatePipelineLayout(gpuContext.device, &pipelineLayoutDesc); - - // Vertex layout - - const WGPUVertexAttribute vertexAttributes[] = {WGPUVertexAttribute{ - .format = WGPUVertexFormat_Float32x2, - .offset = 0, - .shaderLocation = 0, - }}; - - const WGPUVertexBufferLayout vertexBufferLayout{ - .arrayStride = sizeof(float[2]), - .stepMode = WGPUVertexStepMode_Vertex, - .attributeCount = std::size(vertexAttributes), - .attributes = vertexAttributes, - }; - - // Shader module - const WGPUShaderModule shaderModule = [&gpuContext]() -> WGPUShaderModule { const WGPUShaderModuleWGSLDescriptor wgslDesc = { .chain = @@ -1578,82 +1540,45 @@ DeferredRenderer::LightingPass::LightingPass( }(); NLRS_ASSERT(shaderModule != nullptr); - // Fragment state + const WGPUBindGroupLayout bindGroupLayouts[] = { + skyStateBindGroupLayout.ptr(), + uniformBindGroupLayout.ptr(), + gbufferBindGroupLayout.ptr(), + bvhBindGroupLayout.ptr()}; - const WGPUBlendState blendState{ - .color = - WGPUBlendComponent{ - .operation = WGPUBlendOperation_Add, - .srcFactor = WGPUBlendFactor_One, - .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha, - }, - .alpha = - WGPUBlendComponent{ - .operation = WGPUBlendOperation_Add, - .srcFactor = WGPUBlendFactor_Zero, - .dstFactor = WGPUBlendFactor_One, - }, + const WGPUPipelineLayoutDescriptor pipelineLayoutDesc{ + .nextInChain = nullptr, + .label = "Lighting pass pipeline layout", + .bindGroupLayoutCount = std::size(bindGroupLayouts), + .bindGroupLayouts = bindGroupLayouts, }; - const WGPUColorTargetState colorTargets[] = {WGPUColorTargetState{ - .nextInChain = nullptr, - .format = Window::SWAP_CHAIN_FORMAT, - .blend = &blendState, - .writeMask = WGPUColorWriteMask_All}}; + const WGPUPipelineLayout pipelineLayout = + wgpuDeviceCreatePipelineLayout(gpuContext.device, &pipelineLayoutDesc); - const WGPUFragmentState fragmentState{ + const WGPUProgrammableStageDescriptor computeStageDesc{ .nextInChain = nullptr, .module = shaderModule, - .entryPoint = "fsMain", + .entryPoint = "main", .constantCount = 0, .constants = nullptr, - .targetCount = std::size(colorTargets), - .targets = colorTargets, }; - // Pipeline - - const WGPURenderPipelineDescriptor pipelineDesc{ + const WGPUComputePipelineDescriptor pipelineDesc{ .nextInChain = nullptr, - .label = "Lighting pass render pipeline", + .label = "Lighting pass compute pipeline", .layout = pipelineLayout, - .vertex = - WGPUVertexState{ - .nextInChain = nullptr, - .module = shaderModule, - .entryPoint = "vsMain", - .constantCount = 0, - .constants = nullptr, - .bufferCount = 1, - .buffers = &vertexBufferLayout, - }, - .primitive = - WGPUPrimitiveState{ - .nextInChain = nullptr, - .topology = WGPUPrimitiveTopology_TriangleList, - .stripIndexFormat = WGPUIndexFormat_Undefined, - .frontFace = WGPUFrontFace_CCW, - .cullMode = WGPUCullMode_Back, - }, - .depthStencil = nullptr, - .multisample = - WGPUMultisampleState{ - .nextInChain = nullptr, - .count = 1, - .mask = ~0u, - .alphaToCoverageEnabled = false, - }, - .fragment = &fragmentState, + .compute = computeStageDesc, }; - mPipeline = wgpuDeviceCreateRenderPipeline(gpuContext.device, &pipelineDesc); + mPipeline = wgpuDeviceCreateComputePipeline(gpuContext.device, &pipelineDesc); wgpuPipelineLayoutRelease(pipelineLayout); } } DeferredRenderer::LightingPass::~LightingPass() { - renderPipelineSafeRelease(mPipeline); + computePipelineSafeRelease(mPipeline); mPipeline = nullptr; } @@ -1662,7 +1587,6 @@ DeferredRenderer::LightingPass::LightingPass(LightingPass&& other) noexcept if (this != &other) { mCurrentSky = other.mCurrentSky; - mVertexBuffer = std::move(other.mVertexBuffer); mSkyStateBuffer = std::move(other.mSkyStateBuffer); mSkyStateBindGroup = std::move(other.mSkyStateBindGroup); mUniformBuffer = std::move(other.mUniformBuffer); @@ -1686,7 +1610,6 @@ DeferredRenderer::LightingPass& DeferredRenderer::LightingPass::operator=( if (this != &other) { mCurrentSky = other.mCurrentSky; - mVertexBuffer = std::move(other.mVertexBuffer); mSkyStateBuffer = std::move(other.mSkyStateBuffer); mSkyStateBindGroup = std::move(other.mSkyStateBindGroup); mUniformBuffer = std::move(other.mUniformBuffer); @@ -1698,7 +1621,7 @@ DeferredRenderer::LightingPass& DeferredRenderer::LightingPass::operator=( mTextureBuffer = std::move(other.mTextureBuffer); mBlueNoiseBuffer = std::move(other.mBlueNoiseBuffer); mBvhBindGroup = std::move(other.mBvhBindGroup); - renderPipelineSafeRelease(mPipeline); + computePipelineSafeRelease(mPipeline); mPipeline = other.mPipeline; other.mPipeline = nullptr; mFrameCount = other.mFrameCount; @@ -1763,13 +1686,11 @@ void DeferredRenderer::LightingPass::render( }(); NLRS_ASSERT(renderPass != nullptr); - wgpuRenderPassEncoderSetPipeline(renderPass, mPipeline); wgpuRenderPassEncoderSetBindGroup(renderPass, 0, mSkyStateBindGroup.ptr(), 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPass, 1, mUniformBindGroup.ptr(), 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPass, 2, gbufferBindGroup.ptr(), 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPass, 3, mBvhBindGroup.ptr(), 0, nullptr); - wgpuRenderPassEncoderSetVertexBuffer( - renderPass, 0, mVertexBuffer.ptr(), 0, mVertexBuffer.byteSize()); + wgpuRenderPassEncoderDraw(renderPass, 6, 1, 0, 0); gui.render(renderPass); @@ -1829,7 +1750,7 @@ DeferredRenderer::ResolvePass::ResolvePass( const WGPUPipelineLayoutDescriptor pipelineLayoutDesc{ .nextInChain = nullptr, - .label = "Lighting pass pipeline layout", + .label = "Resolve pass pipeline layout", .bindGroupLayoutCount = std::size(bindGroupLayouts), .bindGroupLayouts = bindGroupLayouts, }; @@ -1866,7 +1787,7 @@ DeferredRenderer::ResolvePass::ResolvePass( const WGPUShaderModuleDescriptor moduleDesc{ .nextInChain = &wgslDesc.chain, - .label = "Lighting pass shader", + .label = "Resolve pass shader", }; return wgpuDeviceCreateShaderModule(gpuContext.device, &moduleDesc); diff --git a/src/pt/deferred_renderer.hpp b/src/pt/deferred_renderer.hpp index 7d9f3fd..c13d3f5 100644 --- a/src/pt/deferred_renderer.hpp +++ b/src/pt/deferred_renderer.hpp @@ -159,21 +159,20 @@ class DeferredRenderer struct LightingPass { private: - Sky mCurrentSky = Sky{}; - GpuBuffer mVertexBuffer = GpuBuffer{}; - GpuBuffer mSkyStateBuffer = GpuBuffer{}; - GpuBindGroup mSkyStateBindGroup = GpuBindGroup{}; - GpuBuffer mUniformBuffer = GpuBuffer{}; - GpuBindGroup mUniformBindGroup = GpuBindGroup{}; - GpuBuffer mBvhNodeBuffer = GpuBuffer{}; - GpuBuffer mPositionAttributesBuffer = GpuBuffer{}; - GpuBuffer mVertexAttributesBuffer = GpuBuffer{}; - GpuBuffer mTextureDescriptorBuffer = GpuBuffer{}; - GpuBuffer mTextureBuffer = GpuBuffer{}; - GpuBuffer mBlueNoiseBuffer = GpuBuffer{}; - GpuBindGroup mBvhBindGroup = GpuBindGroup{}; - GpuBindGroup mAccumulationBindGroup = GpuBindGroup{}; - WGPURenderPipeline mPipeline = nullptr; + Sky mCurrentSky = Sky{}; + GpuBuffer mSkyStateBuffer = GpuBuffer{}; + GpuBindGroup mSkyStateBindGroup = GpuBindGroup{}; + GpuBuffer mUniformBuffer = GpuBuffer{}; + GpuBindGroup mUniformBindGroup = GpuBindGroup{}; + GpuBuffer mBvhNodeBuffer = GpuBuffer{}; + GpuBuffer mPositionAttributesBuffer = GpuBuffer{}; + GpuBuffer mVertexAttributesBuffer = GpuBuffer{}; + GpuBuffer mTextureDescriptorBuffer = GpuBuffer{}; + GpuBuffer mTextureBuffer = GpuBuffer{}; + GpuBuffer mBlueNoiseBuffer = GpuBuffer{}; + GpuBindGroup mBvhBindGroup = GpuBindGroup{}; + GpuBindGroup mAccumulationBindGroup = GpuBindGroup{}; + WGPUComputePipeline mPipeline = nullptr; std::uint32_t mFrameCount = 0; diff --git a/src/pt/deferred_renderer_lighting_pass.wgsl b/src/pt/deferred_renderer_lighting_pass.wgsl index 28149e3..69817a0 100644 --- a/src/pt/deferred_renderer_lighting_pass.wgsl +++ b/src/pt/deferred_renderer_lighting_pass.wgsl @@ -1,22 +1,3 @@ -struct VertexInput { - @location(0) position: vec2f, -} - -struct VertexOutput { - @builtin(position) position: vec4f, - @location(0) texCoord: vec2f, -} - -@vertex -fn vsMain(in: VertexInput) -> VertexOutput { - let uv = 0.5 * in.position + vec2f(0.5); - var out: VertexOutput; - out.position = vec4f(in.position, 0.0, 1.0); - out.texCoord = vec2f(uv.x, 1.0 - uv.y); - - return out; -} - struct SkyState { params: array, skyRadiances: array, @@ -32,23 +13,6 @@ struct Uniforms { frameCount: u32, } -@group(0) @binding(0) var skyState: SkyState; - -@group(1) @binding(0) var uniforms: Uniforms; - -@group(2) @binding(0) var gbufferAlbedo: texture_2d; -@group(2) @binding(1) var gbufferNormal: texture_2d; -@group(2) @binding(2) var gbufferDepth: texture_depth_2d; - -@group(3) @binding(0) var bvhNodes: array; -@group(3) @binding(1) var positionAttributes: array; -@group(3) @binding(2) var vertexAttributes: array; -@group(3) @binding(3) var textureDescriptors: array; -@group(3) @binding(4) var textures: array; -@group(3) @binding(5) var blueNoise: BlueNoise; - -@group(4) @binding(0) var accumulationBuffer: array>; - struct Aabb { min: vec3f, max: vec3f, @@ -114,12 +78,30 @@ const SOLAR_INV_PDF = 2f * PI * (1f - SOLAR_COS_THETA_MAX); const T_MIN = 0.001f; const T_MAX = 10000f; -@fragment -fn fsMain(in: VertexOutput) -> @location(0) vec4f { - var color = vec3f(0.0, 0.0, 0.0); +@group(0) @binding(0) var skyState: SkyState; + +@group(1) @binding(0) var uniforms: Uniforms; + +@group(2) @binding(0) var gbufferAlbedo: texture_2d; +@group(2) @binding(1) var gbufferNormal: texture_2d; +@group(2) @binding(2) var gbufferDepth: texture_depth_2d; + +@group(3) @binding(0) var bvhNodes: array; +@group(3) @binding(1) var positionAttributes: array; +@group(3) @binding(2) var vertexAttributes: array; +@group(3) @binding(3) var textureDescriptors: array; +@group(3) @binding(4) var textures: array; +@group(3) @binding(5) var blueNoise: BlueNoise; + +@group(4) @binding(0) var sampleBuffer: array>; + - let uv = in.texCoord; - let textureIdx = vec2u(floor(uv * uniforms.framebufferSize)); +@compute @workgroup_size(64, 64) +fn main(@builtin(global_invocation_id) globalInvocationId: vec3) { + let uv = vec2f(GlobalInvocationID.xy) / uniforms.framebufferSize; + let textureIdx = globalInvocationId.xy; + + var color = vec3f(0.0, 0.0, 0.0); let depthSample = textureLoad(gbufferDepth, textureIdx, 0); if depthSample == 0.0 { let world = worldFromUv(uv, depthSample); @@ -142,9 +124,8 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f { color = surfaceColor(coord, offsetPosition(position, decodedNormal), decodedNormal, albedo); } - let rgb = acesFilmic(uniforms.exposure * color); - let srgb = pow(rgb, vec3(1f / 2.2f)); - return vec4(srgb, 1f); + let sampleBufferIdx = textureIdx.y * u32(uniforms.framebufferSize.x) + textureIdx.x; + sampleBuffer[sampleBufferIdx] = color; } @must_use @@ -253,16 +234,6 @@ fn skyRadiance(theta: f32, gamma: f32, channel: u32) -> f32 { return r * radianceDist + solarRadiance; } -@must_use -fn acesFilmic(x: vec3f) -> vec3f { - let a = 2.51f; - let b = 0.03f; - let c = 2.43f; - let d = 0.59f; - let e = 0.14f; - return saturate((x * (a * x + b)) / (x * (c * x + d) + e)); -} - @must_use fn sampleSolarDiskDirection(u: vec2f, cosThetaMax: f32, direction: vec3f) -> vec3f { let v = directionInCone(u, cosThetaMax); diff --git a/src/pt/deferred_renderer_resolve_pass.wgsl b/src/pt/deferred_renderer_resolve_pass.wgsl index e615655..6662a60 100644 --- a/src/pt/deferred_renderer_resolve_pass.wgsl +++ b/src/pt/deferred_renderer_resolve_pass.wgsl @@ -19,6 +19,7 @@ fn vsMain(in: VertexInput) -> VertexOutput { struct Uniforms { framebufferSize: vec2f, + exposure: f32, } @group(0) @binding(0) var uniforms: Uniforms; @@ -29,5 +30,22 @@ struct Uniforms { @fragment fn fsMain(in: VertexOutput) -> @location(0) vec4f { let uv = in.texCoord; - return vec4(uv, 0.0, 1.0); + let textureIdx = vec2u(floor(uv * uniforms.framebufferSize)); + let sampleBufferIdx = textureIdx.y * u32(uniforms.framebufferSize.x) + textureIdx.x; + let sampleBufferElement = sampleBuffer[sampleBufferIdx]; + let color = vec3f(sampleBufferElement[0], sampleBufferElement[1], sampleBufferElement[2]); + + let rgb = acesFilmic(uniforms.exposure * color); + let srgb = pow(rgb, vec3(1f / 2.2f)); + return vec4(srgb, 1f); +} + +@must_use +fn acesFilmic(x: vec3f) -> vec3f { + let a = 2.51f; + let b = 0.03f; + let c = 2.43f; + let d = 0.59f; + let e = 0.14f; + return saturate((x * (a * x + b)) / (x * (c * x + d) + e)); } diff --git a/src/pt/shader_source.hpp b/src/pt/shader_source.hpp index 0b8f26c..3100495 100644 --- a/src/pt/shader_source.hpp +++ b/src/pt/shader_source.hpp @@ -706,26 +706,7 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f { } )"; -const char* const DEFERRED_RENDERER_LIGHTING_PASS_SOURCE = R"(struct VertexInput { - @location(0) position: vec2f, -} - -struct VertexOutput { - @builtin(position) position: vec4f, - @location(0) texCoord: vec2f, -} - -@vertex -fn vsMain(in: VertexInput) -> VertexOutput { - let uv = 0.5 * in.position + vec2f(0.5); - var out: VertexOutput; - out.position = vec4f(in.position, 0.0, 1.0); - out.texCoord = vec2f(uv.x, 1.0 - uv.y); - - return out; -} - -struct SkyState { +const char* const DEFERRED_RENDERER_LIGHTING_PASS_SOURCE = R"(struct SkyState { params: array, skyRadiances: array, solarRadiances: array, @@ -740,23 +721,6 @@ struct Uniforms { frameCount: u32, } -@group(0) @binding(0) var skyState: SkyState; - -@group(1) @binding(0) var uniforms: Uniforms; - -@group(2) @binding(0) var gbufferAlbedo: texture_2d; -@group(2) @binding(1) var gbufferNormal: texture_2d; -@group(2) @binding(2) var gbufferDepth: texture_depth_2d; - -@group(3) @binding(0) var bvhNodes: array; -@group(3) @binding(1) var positionAttributes: array; -@group(3) @binding(2) var vertexAttributes: array; -@group(3) @binding(3) var textureDescriptors: array; -@group(3) @binding(4) var textures: array; -@group(3) @binding(5) var blueNoise: BlueNoise; - -@group(4) @binding(0) var accumulationBuffer: array>; - struct Aabb { min: vec3f, max: vec3f, @@ -822,12 +786,30 @@ const SOLAR_INV_PDF = 2f * PI * (1f - SOLAR_COS_THETA_MAX); const T_MIN = 0.001f; const T_MAX = 10000f; -@fragment -fn fsMain(in: VertexOutput) -> @location(0) vec4f { - var color = vec3f(0.0, 0.0, 0.0); +@group(0) @binding(0) var skyState: SkyState; - let uv = in.texCoord; - let textureIdx = vec2u(floor(uv * uniforms.framebufferSize)); +@group(1) @binding(0) var uniforms: Uniforms; + +@group(2) @binding(0) var gbufferAlbedo: texture_2d; +@group(2) @binding(1) var gbufferNormal: texture_2d; +@group(2) @binding(2) var gbufferDepth: texture_depth_2d; + +@group(3) @binding(0) var bvhNodes: array; +@group(3) @binding(1) var positionAttributes: array; +@group(3) @binding(2) var vertexAttributes: array; +@group(3) @binding(3) var textureDescriptors: array; +@group(3) @binding(4) var textures: array; +@group(3) @binding(5) var blueNoise: BlueNoise; + +@group(4) @binding(0) var sampleBuffer: array>; + + +@compute @workgroup_size(64, 64) +fn main(@builtin(global_invocation_id) globalInvocationId: vec3) { + let uv = vec2f(GlobalInvocationID.xy) / uniforms.framebufferSize; + let textureIdx = globalInvocationId.xy; + + var color = vec3f(0.0, 0.0, 0.0); let depthSample = textureLoad(gbufferDepth, textureIdx, 0); if depthSample == 0.0 { let world = worldFromUv(uv, depthSample); @@ -850,9 +832,8 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f { color = surfaceColor(coord, offsetPosition(position, decodedNormal), decodedNormal, albedo); } - let rgb = acesFilmic(uniforms.exposure * color); - let srgb = pow(rgb, vec3(1f / 2.2f)); - return vec4(srgb, 1f); + let sampleBufferIdx = textureIdx.y * u32(uniforms.framebufferSize.x) + textureIdx.x; + sampleBuffer[sampleBufferIdx] = color; } @must_use @@ -961,16 +942,6 @@ fn skyRadiance(theta: f32, gamma: f32, channel: u32) -> f32 { return r * radianceDist + solarRadiance; } -@must_use -fn acesFilmic(x: vec3f) -> vec3f { - let a = 2.51f; - let b = 0.03f; - let c = 2.43f; - let d = 0.59f; - let e = 0.14f; - return saturate((x * (a * x + b)) / (x * (c * x + d) + e)); -} - @must_use fn sampleSolarDiskDirection(u: vec2f, cosThetaMax: f32, direction: vec3f) -> vec3f { let v = directionInCone(u, cosThetaMax); @@ -1235,8 +1206,7 @@ fn rayIntersectTriangle(ray: Ray, tri: Positions, tmax: f32, hit: ptr vec3f { @@ -1245,7 +1215,8 @@ fn offsetPosition(p: vec3f, n: vec3f) -> vec3f { // Offset added straight into the mantissa bits to ensure the offset is scale-invariant, // except for when close to the origin, where we use FLOAT_SCALE as a small epsilon. let po = vec3f( - bitcast(bitcast(p.x) + select(offset.x, -offset.x, (p.x < 0))), + bitcast(bitcast(p.x) + select(offset.x, -offset.x, (p.x <)" +R"( 0))), bitcast(bitcast(p.y) + select(offset.y, -offset.y, (p.y < 0))), bitcast(bitcast(p.z) + select(offset.z, -offset.z, (p.z < 0))) ); @@ -1322,6 +1293,7 @@ fn vsMain(in: VertexInput) -> VertexOutput { struct Uniforms { framebufferSize: vec2f, + exposure: f32, } @group(0) @binding(0) var uniforms: Uniforms; @@ -1332,7 +1304,24 @@ struct Uniforms { @fragment fn fsMain(in: VertexOutput) -> @location(0) vec4f { let uv = in.texCoord; - return vec4(uv, 0.0, 1.0); + let textureIdx = vec2u(floor(uv * uniforms.framebufferSize)); + let sampleBufferIdx = textureIdx.y * u32(uniforms.framebufferSize.x) + textureIdx.x; + let sampleBufferElement = sampleBuffer[sampleBufferIdx]; + let color = vec3f(sampleBufferElement[0], sampleBufferElement[1], sampleBufferElement[2]); + + let rgb = acesFilmic(uniforms.exposure * color); + let srgb = pow(rgb, vec3(1f / 2.2f)); + return vec4(srgb, 1f); +} + +@must_use +fn acesFilmic(x: vec3f) -> vec3f { + let a = 2.51f; + let b = 0.03f; + let c = 2.43f; + let d = 0.59f; + let e = 0.14f; + return saturate((x * (a * x + b)) / (x * (c * x + d) + e)); } )"; diff --git a/src/pt/webgpu_utils.hpp b/src/pt/webgpu_utils.hpp index 5e9c8b3..afa245f 100644 --- a/src/pt/webgpu_utils.hpp +++ b/src/pt/webgpu_utils.hpp @@ -46,6 +46,14 @@ inline void renderPipelineSafeRelease(const WGPURenderPipeline pipeline) noexcep } } +inline void computePipelineSafeRelease(const WGPUComputePipeline pipeline) noexcept +{ + if (pipeline) + { + wgpuComputePipelineRelease(pipeline); + } +} + inline void samplerSafeRelease(const WGPUSampler sampler) noexcept { if (sampler)