From 24c7d9b8b65f0076e7d245e6feb2da55932b6111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Stompo=CC=81r?= Date: Sun, 28 Sep 2025 23:53:37 +0200 Subject: [PATCH] Support lower resolution for bloom --- Engine/Core/Configuration/PNDefaults.swift | 4 +++- .../Rendering/Jobs/Bloom/PNBloomSplitJob.swift | 6 +++--- Engine/Core/Rendering/Stages/PNPipeline.swift | 1 + .../Rendering/Stages/PNPostprocessStage.swift | 16 +++++++++------- Engine/Shaders/Bloom/BloomSplit.metal | 14 ++++++++------ Engine/Shaders/Postprocess/Grain.h | 2 +- Engine/Shaders/Postprocess/Grain.metal | 4 ++-- .../Shaders/Postprocess/PostprocessMerge.metal | 15 +++++++++------ Engine/Shaders/Postprocess/Vignette.h | 2 +- Engine/Shaders/Postprocess/Vignette.metal | 4 ++-- 10 files changed, 39 insertions(+), 29 deletions(-) diff --git a/Engine/Core/Configuration/PNDefaults.swift b/Engine/Core/Configuration/PNDefaults.swift index c63ab41c..70b22611 100644 --- a/Engine/Core/Configuration/PNDefaults.swift +++ b/Engine/Core/Configuration/PNDefaults.swift @@ -72,7 +72,9 @@ public struct PNDefaults { /// Amplifies the brightness of pixels that pass the luminance threshold for bloom. public var luminanceAmplifier: Float16 = 1.2 /// The sigma value for the Gaussian blur applied to blooming areas. - public var blurSigma: Float = 10.0 + public var blurSigma: Float = 1.0 + /// Rendering scale - scale of the image in relation to frame resolution + public var renderingScale: Float = 0.33 } /// Configuration for Screen Space Ambient Occlusion (SSAO) visual effect. /// SSAO simulates subtle self-shadowing in creases, holes, and surfaces to enhance realism in rendered images. These parameters control the visual quality and performance of the effect. diff --git a/Engine/Core/Rendering/Jobs/Bloom/PNBloomSplitJob.swift b/Engine/Core/Rendering/Jobs/Bloom/PNBloomSplitJob.swift index 33640b21..3377d46a 100644 --- a/Engine/Core/Rendering/Jobs/Bloom/PNBloomSplitJob.swift +++ b/Engine/Core/Rendering/Jobs/Bloom/PNBloomSplitJob.swift @@ -18,11 +18,11 @@ struct PNBloomSplitJob: PNComputeJob { self.pipelineState = pipelineState self.inputTexture = inputTexture self.outputTexture = outputTexture - guard let inputTexture = inputTexture.texture else { + guard let outputTexture = outputTexture.texture else { return nil } - dispatchSize = MTLSize(width: inputTexture.width, - height: inputTexture.height) + dispatchSize = MTLSize(width: outputTexture.width, + height: outputTexture.height) } func compute(encoder: MTLComputeCommandEncoder, supply: PNFrameSupply) { encoder.setComputePipelineState(pipelineState) diff --git a/Engine/Core/Rendering/Stages/PNPipeline.swift b/Engine/Core/Rendering/Stages/PNPipeline.swift index 5ee9739b..71c101a4 100644 --- a/Engine/Core/Rendering/Stages/PNPipeline.swift +++ b/Engine/Core/Rendering/Stages/PNPipeline.swift @@ -50,6 +50,7 @@ class PNPipeline: PNStage { let postprocessStage = PNPostprocessStage(input: combineStage.io.output.color[0], velocities: gBufferStage.io.output.color[3], bloomBlurSigma: PNDefaults.shared.shaders.postprocess.bloom.blurSigma, + bloomRenderingScale: PNDefaults.shared.shaders.postprocess.bloom.renderingScale, device: device, renderingSize: renderingSize) else { return nil diff --git a/Engine/Core/Rendering/Stages/PNPostprocessStage.swift b/Engine/Core/Rendering/Stages/PNPostprocessStage.swift index 12962e8f..b17500f5 100644 --- a/Engine/Core/Rendering/Stages/PNPostprocessStage.swift +++ b/Engine/Core/Rendering/Stages/PNPostprocessStage.swift @@ -16,12 +16,14 @@ struct PNPostprocessStage: PNStage { init?(input: PNTextureProvider, velocities: PNTextureProvider, bloomBlurSigma: Float, + bloomRenderingScale: Float, device: MTLDevice, renderingSize: CGSize) { - guard let bloomSplitTexture = device.makeTextureBloomSplitC(size: renderingSize) else { - return nil - } - guard let postprocessOutputTexture = device.makeTexturePostprocessOutput(size: renderingSize) else { + let bloomSplitTextureSize = CGSize(width: renderingSize.width * CGFloat(bloomRenderingScale), + height: renderingSize.height * CGFloat(bloomRenderingScale)) + + guard let bloomSplitTexture = device.makeTextureBloomSplitC(size: bloomSplitTextureSize), + let postprocessOutputTexture = device.makeTexturePostprocessOutput(size: renderingSize) else { return nil } self.bloomSplitTexture = bloomSplitTexture @@ -29,7 +31,7 @@ struct PNPostprocessStage: PNStage { guard let bloomSplitJob = PNBloomSplitJob.make(device: device, inputTexture: input, outputTexture: PNStaticTexture(bloomSplitTexture)), - let splitBlurredTexture = device.makeTexture(descriptor: .bloomSplitC(size: renderingSize)), + let splitBlurredTexture = device.makeTexture(descriptor: .bloomSplitC(size: bloomSplitTextureSize)), let postprocessMergeJob = PNPostprocessMergeJob.make(device: device, sceneTexture: input, velocities: velocities, @@ -47,7 +49,7 @@ struct PNPostprocessStage: PNStage { } func draw(commandBuffer: MTLCommandBuffer, supply: PNFrameSupply) { guard let bloomSplitEncoder = commandBuffer.makeComputeCommandEncoder() else { - return + fatalError("Failed to create an encoder for bloom split") } bloomSplitJob.compute(encoder: bloomSplitEncoder, supply: supply) bloomSplitEncoder.endEncoding() @@ -55,7 +57,7 @@ struct PNPostprocessStage: PNStage { sourceTexture: bloomSplitTexture, destinationTexture: splitBlurredTexture) guard let postprocessMergeEncoder = commandBuffer.makeComputeCommandEncoder() else { - return + fatalError("Failed to create an encoder for postprocess merge") } postprocessMergeJob.compute(encoder: postprocessMergeEncoder, supply: supply) postprocessMergeEncoder.endEncoding() diff --git a/Engine/Shaders/Bloom/BloomSplit.metal b/Engine/Shaders/Bloom/BloomSplit.metal index 613b7011..ef2432a2 100644 --- a/Engine/Shaders/Bloom/BloomSplit.metal +++ b/Engine/Shaders/Bloom/BloomSplit.metal @@ -16,10 +16,12 @@ using namespace metal; kernel void kernelBloomSplit(texture2d inputTexture [[texture(kAttributeBloomSplitComputeShaderTextureInput)]], texture2d outputTexture [[texture(kAttributeBloomSplitComputeShaderTextureOutput)]], uint2 inposition [[thread_position_in_grid]]) { - half3 color = inputTexture.read(inposition.xy).xyz; - if (luminance(color) > luminanceThreshold) { - outputTexture.write(half4(color * amplification, 1.0h), inposition.xy); - } else { - outputTexture.write(half4(0.0h, 0.0h, 0.0h, 1.0h), inposition.xy); - } + float2 texcoord = float2(inposition.x / static_cast(outputTexture.get_width()), + inposition.y / static_cast(outputTexture.get_height())); + float2 resolution = float2(inputTexture.get_width(), inputTexture.get_height()); + uint2 inTexel = uint2(texcoord * resolution); + half3 color = inputTexture.read(inTexel).rgb; + half mask = luminance(color) > luminanceThreshold; + half3 amplifiedColor = color * amplification; + outputTexture.write(half4(mask * amplifiedColor, 1.0h), inposition.xy); } diff --git a/Engine/Shaders/Postprocess/Grain.h b/Engine/Shaders/Postprocess/Grain.h index d68acd50..7633e3dc 100644 --- a/Engine/Shaders/Postprocess/Grain.h +++ b/Engine/Shaders/Postprocess/Grain.h @@ -7,5 +7,5 @@ #include simd::half3 grain(float time, - simd::half2 texcoord, + simd::float2 texcoord, simd::half3 inputColor); diff --git a/Engine/Shaders/Postprocess/Grain.metal b/Engine/Shaders/Postprocess/Grain.metal index 271e82c5..a14f3836 100644 --- a/Engine/Shaders/Postprocess/Grain.metal +++ b/Engine/Shaders/Postprocess/Grain.metal @@ -15,9 +15,9 @@ using namespace metal; #define MEAN 0.0h #define VARIANCE 0.5h -half3 grain(float time, half2 texcoord, half3 inputColor) { +half3 grain(float time, float2 texcoord, half3 inputColor) { float t = time * float(SPEED); - float seed = dot(float2(texcoord), float2(12.9898, 78.233)); + float seed = dot(texcoord, float2(12.9898, 78.233)); float noise = fract(sin(seed) * 43758.5453 + t); noise = gaussian(noise, float(MEAN), float(VARIANCE) * float(VARIANCE)); half3 grain = half3(noise) * (1.0h - inputColor.xyz); diff --git a/Engine/Shaders/Postprocess/PostprocessMerge.metal b/Engine/Shaders/Postprocess/PostprocessMerge.metal index ea843ca8..f401a8d9 100644 --- a/Engine/Shaders/Postprocess/PostprocessMerge.metal +++ b/Engine/Shaders/Postprocess/PostprocessMerge.metal @@ -12,16 +12,16 @@ using namespace metal; -kernel void postprocessMerge(texture2d inputTexture [[texture(kAttributePostprocessMergeComputeShaderTextureOriginal)]], - texture2d brightAreasTexture [[texture(kAttributePostprocessMergeComputeShaderTextureBrightAreas)]], - texture2d velocityTexture [[texture(kAttributePostprocessMergeComputeShaderTextureVelocities)]], +kernel void postprocessMerge(texture2d inputTexture [[texture(kAttributePostprocessMergeComputeShaderTextureOriginal)]], + texture2d brightAreasTexture [[texture(kAttributePostprocessMergeComputeShaderTextureBrightAreas)]], + texture2d velocityTexture [[texture(kAttributePostprocessMergeComputeShaderTextureVelocities)]], texture2d outputTexture [[texture(kAttributePostprocessMergeComputeShaderTextureOutput)]], constant float & time [[buffer(kAttributePostprocessMergeComputeShaderBufferTime)]], uint2 inposition [[thread_position_in_grid]], uint2 threads [[threads_per_grid]]) { - half2 texcoord{half(inposition.x)/half(threads.x), - half(inposition.y)/half(threads.y)}; + float2 texcoord{float(inposition.x)/float(threads.x), + float(inposition.y)/float(threads.y)}; half3 blurredImage = motionBlur(inputTexture, velocityTexture, @@ -29,7 +29,10 @@ kernel void postprocessMerge(texture2d inputTexture [[texture(kAttributePo 1.0h, 5).rgb; - half3 bloomColor = brightAreasTexture.read(inposition.xy).xyz; + float2 bloomResolution = float2(brightAreasTexture.get_width(), + brightAreasTexture.get_height()); + uint2 bloomTexel = uint2(bloomResolution * texcoord); + half3 bloomColor = brightAreasTexture.read(bloomTexel).xyz; half4 inputColor = half4(bloomColor + blurredImage, 1); half4 vignetteColor = vignette(inputColor, half4(0, 0, 0, 1), texcoord, 0.8h, 2.0h); half4 grainColor = half4(grain(time, texcoord, vignetteColor.xyz), 1.0h); diff --git a/Engine/Shaders/Postprocess/Vignette.h b/Engine/Shaders/Postprocess/Vignette.h index 0a447e41..c72db2cc 100644 --- a/Engine/Shaders/Postprocess/Vignette.h +++ b/Engine/Shaders/Postprocess/Vignette.h @@ -8,6 +8,6 @@ metal::half4 vignette(metal::half4 fragmentColor, metal::half4 vignetteColor, - metal::half2 position, + metal::float2 position, half fromRadius, half toRadius); diff --git a/Engine/Shaders/Postprocess/Vignette.metal b/Engine/Shaders/Postprocess/Vignette.metal index 981a5b9d..c5e6be00 100644 --- a/Engine/Shaders/Postprocess/Vignette.metal +++ b/Engine/Shaders/Postprocess/Vignette.metal @@ -8,10 +8,10 @@ using namespace metal; half4 vignette(half4 fragmentColor, half4 vignetteColor, - half2 position, + float2 position, half fromRadius, half toRadius) { - half radius = length(half2(0.5h, 0.5h) - position) * 2.0h; + half radius = length(float2(0.5f, 0.5f) - position) * 2.0h; half ratio = saturate(radius - fromRadius) / (toRadius - fromRadius); return mix(fragmentColor, vignetteColor, ratio); }