Add Unity-style buffer shader resource support

This commit is contained in:
2026-04-08 18:29:16 +08:00
parent 4728b09ae8
commit bb0f4afe7d
8 changed files with 420 additions and 10 deletions

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/Containers/Array.h>
#include "Rendering/Detail/ShaderVariantUtils.h"
#include <chrono>
#include <cstring>
@@ -497,6 +498,193 @@ TEST(ShaderLoader, LoadShaderAuthoringBuildsGenericHlslVariants) {
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadShaderAuthoringCollectsUnityStyleBufferResources) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_authoring_buffer_resources_test";
const fs::path shaderPath = shaderRoot / "buffer_resources.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderPath,
R"(Shader "Test/BufferResources"
{
SubShader
{
Pass
{
Name "Volume"
HLSLPROGRAM
#pragma target 4.5
#pragma vertex MainVS
#pragma fragment MainPS
StructuredBuffer<float4> InputBuffer;
ByteAddressBuffer RawInput;
RWStructuredBuffer<uint> OutputBuffer;
RWByteAddressBuffer OutputRaw;
struct VSInput
{
float3 positionOS : POSITION;
};
float4 MainVS(VSInput input) : SV_POSITION
{
return float4(input.positionOS, 1.0);
}
float4 MainPS() : SV_TARGET
{
return float4(1.0, 1.0, 1.0, 1.0);
}
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("Volume");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->resources.Size(), 4u);
const ShaderResourceBindingDesc* inputBuffer =
shader->FindPassResourceBinding("Volume", "InputBuffer");
ASSERT_NE(inputBuffer, nullptr);
EXPECT_EQ(inputBuffer->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(inputBuffer->set, 2u);
EXPECT_EQ(inputBuffer->binding, 0u);
const ShaderResourceBindingDesc* rawInput =
shader->FindPassResourceBinding("Volume", "RawInput");
ASSERT_NE(rawInput, nullptr);
EXPECT_EQ(rawInput->type, ShaderResourceType::RawBuffer);
EXPECT_EQ(rawInput->set, 2u);
EXPECT_EQ(rawInput->binding, 1u);
const ShaderResourceBindingDesc* outputBuffer =
shader->FindPassResourceBinding("Volume", "OutputBuffer");
ASSERT_NE(outputBuffer, nullptr);
EXPECT_EQ(outputBuffer->type, ShaderResourceType::RWStructuredBuffer);
EXPECT_EQ(outputBuffer->set, 4u);
EXPECT_EQ(outputBuffer->binding, 0u);
const ShaderResourceBindingDesc* outputRaw =
shader->FindPassResourceBinding("Volume", "OutputRaw");
ASSERT_NE(outputRaw, nullptr);
EXPECT_EQ(outputRaw->type, ShaderResourceType::RWRawBuffer);
EXPECT_EQ(outputRaw->set, 4u);
EXPECT_EQ(outputRaw->binding, 1u);
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, RuntimeShaderSourceRewritesUnityStyleBufferRegisters) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_authoring_buffer_runtime_test";
const fs::path shaderPath = shaderRoot / "buffer_runtime.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderPath,
R"(Shader "Test/BufferRuntime"
{
SubShader
{
Pass
{
Name "Volume"
HLSLPROGRAM
#pragma target 4.5
#pragma vertex MainVS
#pragma fragment MainPS
StructuredBuffer<float4> InputBuffer;
ByteAddressBuffer RawInput;
RWStructuredBuffer<uint> OutputBuffer;
RWByteAddressBuffer OutputRaw;
struct VSInput
{
float3 positionOS : POSITION;
};
float4 MainVS(VSInput input) : SV_POSITION
{
return float4(input.positionOS, 1.0);
}
float4 MainPS() : SV_TARGET
{
return float4(1.0, 0.5, 0.25, 1.0);
}
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("Volume");
ASSERT_NE(pass, nullptr);
const ShaderStageVariant* fragmentVariant =
shader->FindVariant("Volume", ShaderType::Fragment, ShaderBackend::D3D12);
ASSERT_NE(fragmentVariant, nullptr);
const std::string d3d12Source =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
*pass,
ShaderBackend::D3D12,
*fragmentVariant);
EXPECT_NE(
d3d12Source.find("StructuredBuffer<float4> InputBuffer: register(t0);"),
std::string::npos);
EXPECT_NE(
d3d12Source.find("ByteAddressBuffer RawInput: register(t1);"),
std::string::npos);
EXPECT_NE(
d3d12Source.find("RWStructuredBuffer<uint> OutputBuffer: register(u0);"),
std::string::npos);
EXPECT_NE(
d3d12Source.find("RWByteAddressBuffer OutputRaw: register(u1);"),
std::string::npos);
const ShaderStageVariant* vulkanFragment =
shader->FindVariant("Volume", ShaderType::Fragment, ShaderBackend::Vulkan);
ASSERT_NE(vulkanFragment, nullptr);
const std::string vulkanSource =
::XCEngine::Rendering::Detail::BuildRuntimeShaderSource(
*pass,
ShaderBackend::Vulkan,
*vulkanFragment);
EXPECT_NE(
vulkanSource.find("StructuredBuffer<float4> InputBuffer: register(t0, space2);"),
std::string::npos);
EXPECT_NE(
vulkanSource.find("ByteAddressBuffer RawInput: register(t1, space2);"),
std::string::npos);
EXPECT_NE(
vulkanSource.find("RWStructuredBuffer<uint> OutputBuffer: register(u0, space4);"),
std::string::npos);
EXPECT_NE(
vulkanSource.find("RWByteAddressBuffer OutputRaw: register(u1, space4);"),
std::string::npos);
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadShaderAuthoringParsesPassStateAndFallback) {
namespace fs = std::filesystem;