Tests/ComputeTest: Load shader from file (and support hot-reload)

This commit is contained in:
SirLynix 2022-12-29 12:04:11 +01:00
parent 4b804dc613
commit 98f2feecc7
3 changed files with 137 additions and 97 deletions

View File

@ -0,0 +1,59 @@
[nzsl_version("1.0")]
module Compute.EdgeDetection;
external
{
[binding(0)] input_tex: texture2D[f32, readonly, rgba8],
[binding(1)] output_tex: texture2D[f32, writeonly, rgba8]
}
struct Input
{
[builtin(global_invocation_indices)] global_invocation_id: vec3[u32]
}
[entry(compute)]
[workgroup(32, 32, 1)]
fn main(input: Input)
{
let indices = vec2[i32](input.global_invocation_id.xy);
// Fetch neighbouring texels
let avg: array[f32, 9];
let n = 0;
[unroll]
for i in -1 -> 2
{
[unroll]
for j in -1 -> 2
{
let rgb = input_tex.Read(indices + vec2[i32](i, j)).rgb;
avg[n] = (rgb.r + rgb.b + rgb.b) / 3.0;
n += 1;
}
}
let kernel: array[f32, 9];
[unroll]
for i in 0 -> 9
{
if (i == 4)
kernel[i] = 1.0;
else
kernel[i] = -1.0/8.0;
}
let res = vec4[f32](conv(kernel, avg, 0.1, 0.0).rrr, 1.0);
output_tex.Write(indices, res);
}
fn conv(kernel: array[f32, 9], data: array[f32, 9], denom: f32, offset: f32) -> f32
{
let res = 0.0;
[unroll]
for i in 0 -> 9
res += kernel[i] * data[i];
return clamp(res/denom + offset, 0.0, 1.0);
}

View File

@ -0,0 +1,32 @@
[nzsl_version("1.0")]
module Compute.Sepia;
external
{
[binding(0)] input_tex: texture2D[f32, readonly, rgba8],
[binding(1)] output_tex: texture2D[f32, writeonly, rgba8]
}
struct Input
{
[builtin(global_invocation_indices)] global_invocation_id: vec3[u32]
}
[entry(compute)]
[workgroup(32, 32, 1)]
fn main(input: Input)
{
let indices = vec2[i32](input.global_invocation_id.xy);
let inputColor = input_tex.Read(indices).rgb;
let outputColor = vec4[f32]
(
inputColor.x * 0.393 + inputColor.y * 0.769 + inputColor.z * 0.189,
inputColor.x * 0.349 + inputColor.y * 0.686 + inputColor.z * 0.168,
inputColor.x * 0.372 + inputColor.y * 0.534 + inputColor.z * 0.131,
1.0
);
output_tex.Write(indices, outputColor);
}

View File

@ -2,6 +2,7 @@
#include <Nazara/Math.hpp>
#include <Nazara/Platform.hpp>
#include <Nazara/Renderer.hpp>
#include <NZSL/FilesystemModuleResolver.hpp>
#include <NZSL/LangWriter.hpp>
#include <NZSL/Parser.hpp>
#include <Nazara/Utility.hpp>
@ -12,12 +13,6 @@
NAZARA_REQUEST_DEDICATED_GPU()
struct ComputePipeline
{
std::shared_ptr<Nz::RenderPipelineLayout> layout;
std::shared_ptr<Nz::ComputePipeline> pipeline;
};
struct SpriteRenderData
{
std::shared_ptr<Nz::RenderBuffer> vertexBuffer;
@ -31,7 +26,7 @@ struct SpriteRenderPipeline
};
ComputePipeline BuildComputePipeline(Nz::RenderDevice& device);
std::shared_ptr<Nz::ComputePipeline> BuildComputePipeline(Nz::RenderDevice& device, std::shared_ptr<Nz::RenderPipelineLayout> pipelineLayout, std::shared_ptr<nzsl::ModuleResolver> moduleResolver);
SpriteRenderPipeline BuildSpritePipeline(Nz::RenderDevice& device);
SpriteRenderData BuildSpriteData(Nz::RenderDevice& device, const SpriteRenderPipeline& pipelineData, const Nz::Rectf& textureRect, const Nz::Vector2f& screenSize, const Nz::Texture& texture, const Nz::TextureSampler& sampler);
@ -50,7 +45,6 @@ int main()
Nz::Modules<Nz::Renderer> nazara(rendererConfig);
Nz::RenderDeviceFeatures enabledFeatures;
enabledFeatures.computeShaders = true;
enabledFeatures.textureReadWrite = true;
@ -73,8 +67,30 @@ int main()
std::shared_ptr<Nz::TextureSampler> textureSampler = device->InstantiateTextureSampler({});
// Compute part
ComputePipeline computePipeline = BuildComputePipeline(*device);
std::shared_ptr<Nz::ShaderBinding> computeBinding = computePipeline.layout->AllocateShaderBinding(0);
Nz::RenderPipelineLayoutInfo computePipelineLayoutInfo;
computePipelineLayoutInfo.bindings.assign({
{
0, 0, 1,
Nz::ShaderBindingType::Texture,
nzsl::ShaderStageType::Compute
},
{
0, 1, 1,
Nz::ShaderBindingType::Texture,
nzsl::ShaderStageType::Compute
},
});
std::shared_ptr<Nz::RenderPipelineLayout> computePipelineLayout = device->InstantiateRenderPipelineLayout(computePipelineLayoutInfo);
std::shared_ptr<nzsl::FilesystemModuleResolver> moduleResolver = std::make_shared<nzsl::FilesystemModuleResolver>();
moduleResolver->RegisterModuleDirectory(resourceDir / "../shaders/", true);
std::shared_ptr<Nz::ComputePipeline> computePipeline = BuildComputePipeline(*device, computePipelineLayout, moduleResolver);
std::shared_ptr<Nz::ComputePipeline> newComputePipeline;
std::atomic_bool hasNewPipeline = false;
std::shared_ptr<Nz::ShaderBinding> computeBinding = computePipelineLayout->AllocateShaderBinding(0);
computeBinding->Update({
{
0,
@ -92,6 +108,14 @@ int main()
}
});
moduleResolver->OnModuleUpdated.Connect([&](nzsl::ModuleResolver*, const std::string& moduleName)
{
std::cout << moduleName << " has been updated" << std::endl;
newComputePipeline = BuildComputePipeline(*device, computePipelineLayout, moduleResolver);
hasNewPipeline = true;
});
std::string windowTitle = "Compute test";
Nz::RenderWindow window;
@ -123,15 +147,22 @@ int main()
continue;
}
if (hasNewPipeline)
{
frame.PushForRelease(std::move(computePipeline));
computePipeline = std::move(newComputePipeline);
hasNewPipeline = false;
}
const Nz::RenderTarget* windowRT = window.GetRenderTarget();
frame.Execute([&](Nz::CommandBufferBuilder& builder)
{
builder.BeginDebugRegion("Compute part", Nz::Color::Blue);
builder.BeginDebugRegion("Compute part", Nz::Color::Blue());
{
builder.TextureBarrier(Nz::PipelineStage::FragmentShader, Nz::PipelineStage::ComputeShader, Nz::MemoryAccess::ShaderRead, Nz::MemoryAccess::ShaderRead, Nz::TextureLayout::ColorInput, Nz::TextureLayout::General, *texture);
builder.TextureBarrier(Nz::PipelineStage::FragmentShader, Nz::PipelineStage::ComputeShader, Nz::MemoryAccess::ShaderRead, Nz::MemoryAccess::ShaderWrite, Nz::TextureLayout::Undefined, Nz::TextureLayout::General, *targetTexture);
builder.BindComputePipeline(*computePipeline.pipeline);
builder.BindComputePipeline(*computePipeline);
builder.BindComputeShaderBinding(0, *computeBinding);
builder.Dispatch(destTexParams.width / 32, destTexParams.height / 32, 1);
@ -187,74 +218,11 @@ int main()
return EXIT_SUCCESS;
}
// Edge detection, translated to NZSL from Sascha Willems compute shader example
const char computeSource[] = R"(
[nzsl_version("1.0")]
module;
external
{
[binding(0)] input_tex: texture2D[f32, readonly, rgba8],
[binding(1)] output_tex: texture2D[f32, writeonly, rgba8]
}
struct Input
{
[builtin(global_invocation_indices)] global_invocation_id: vec3[u32]
}
[entry(compute)]
[workgroup(32, 32, 1)]
fn main(input: Input)
{
let indices = vec2[i32](input.global_invocation_id.xy);
// Fetch neighbouring texels
let avg: array[f32, 9];
let n = 0;
[unroll]
for i in -1 -> 2
{
[unroll]
for j in -1 -> 2
{
let rgb = input_tex.Read(indices + vec2[i32](i, j)).rgb;
avg[n] = (rgb.r + rgb.b + rgb.b) / 3.0;
n += 1;
}
}
let kernel: array[f32, 9];
[unroll]
for i in 0 -> 9
{
if (i == 4)
kernel[i] = 1.0;
else
kernel[i] = -1.0/8.0;
}
let res = vec4[f32](conv(kernel, avg, 0.1, 0.0).rrr, 1.0);
output_tex.Write(indices, res);
}
fn conv(kernel: array[f32, 9], data: array[f32, 9], denom: f32, offset: f32) -> f32
{
let res = 0.0;
[unroll]
for i in 0 -> 9
res += kernel[i] * data[i];
return clamp(res/denom + offset, 0.0, 1.0);
}
)";
ComputePipeline BuildComputePipeline(Nz::RenderDevice& device)
std::shared_ptr<Nz::ComputePipeline> BuildComputePipeline(Nz::RenderDevice& device, std::shared_ptr<Nz::RenderPipelineLayout> pipelineLayout, std::shared_ptr<nzsl::ModuleResolver> moduleResolver)
{
try
{
nzsl::Ast::ModulePtr shaderModule = nzsl::Parse(std::string_view(computeSource, sizeof(computeSource)));
nzsl::Ast::ModulePtr shaderModule = moduleResolver->Resolve("Compute.Sepia");
if (!shaderModule)
{
std::cout << "Failed to parse shader module" << std::endl;
@ -271,22 +239,6 @@ ComputePipeline BuildComputePipeline(Nz::RenderDevice& device)
std::abort();
}
Nz::RenderPipelineLayoutInfo computePipelineLayoutInfo;
auto& inputBinding = computePipelineLayoutInfo.bindings.emplace_back();
inputBinding.setIndex = 0;
inputBinding.bindingIndex = 0;
inputBinding.shaderStageFlags = nzsl::ShaderStageType::Compute;
inputBinding.type = Nz::ShaderBindingType::Texture;
auto& outputBinding = computePipelineLayoutInfo.bindings.emplace_back();
outputBinding.setIndex = 0;
outputBinding.bindingIndex = 1;
outputBinding.shaderStageFlags = nzsl::ShaderStageType::Compute;
outputBinding.type = Nz::ShaderBindingType::Texture;
std::shared_ptr<Nz::RenderPipelineLayout> pipelineLayout = device.InstantiateRenderPipelineLayout(computePipelineLayoutInfo);
Nz::ComputePipelineInfo computePipelineInfo;
computePipelineInfo.pipelineLayout = pipelineLayout;
computePipelineInfo.shaderModule = computeShader;
@ -298,10 +250,7 @@ ComputePipeline BuildComputePipeline(Nz::RenderDevice& device)
std::abort();
}
ComputePipeline result;
result.layout = std::move(pipelineLayout);
result.pipeline = std::move(pipeline);
return result;
return pipeline;
}
catch (const std::exception& e)
{