Merge pull request #133 from oneandonlydean/feat/shader-ngg-mesh
feat(shader): translate NGG geometry programs to mesh shaders
This commit is contained in:
commit
14b883dbbb
|
|
@ -46,7 +46,7 @@ add_custom_command(
|
|||
VERBATIM
|
||||
)
|
||||
|
||||
add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
|
||||
set(agcDriverSources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Unimplemented.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/DisplayBuffer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver.cpp
|
||||
|
|
@ -95,6 +95,10 @@ add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/Submit/src/Dcb.cpp
|
||||
)
|
||||
|
||||
add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
|
||||
${agcDriverSources}
|
||||
)
|
||||
|
||||
target_include_directories(libSceAgcDriver PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${agcTextureDetileGeneratedRoot}
|
||||
|
|
@ -303,6 +307,20 @@ target_link_libraries(agc_driver_bda_device_tests PRIVATE SDL2-static libc shade
|
|||
set_target_properties(agc_driver_bda_device_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_bda_device_tests)
|
||||
|
||||
add_executable(agc_driver_mesh_tests EXCLUDE_FROM_ALL
|
||||
tests/Mesh.cpp
|
||||
${agcDriverSources}
|
||||
)
|
||||
target_include_directories(agc_driver_mesh_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${agcTextureDetileGeneratedRoot}
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/SPIRV-Headers/include
|
||||
)
|
||||
target_link_libraries(agc_driver_mesh_tests PRIVATE shader_recompiler_agc_driver SDL2-static libc libkernel)
|
||||
set_target_properties(agc_driver_mesh_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_mesh_tests)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_dpp_write_tests agc_shader_disk_cache_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests)
|
||||
string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}")
|
||||
|
|
|
|||
|
|
@ -1426,7 +1426,7 @@ private:
|
|||
const auto sameMesh = [](const std::optional<ShaderRecompiler::MeshConfiguration>& x, const std::optional<ShaderRecompiler::MeshConfiguration>& y) {
|
||||
if (x.has_value() != y.has_value()) return false;
|
||||
if (!x) return true;
|
||||
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex;
|
||||
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize;
|
||||
};
|
||||
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
|
||||
if (x.has_value() != y.has_value()) return false;
|
||||
|
|
@ -4203,6 +4203,15 @@ private:
|
|||
const auto& graphics = decode->state;
|
||||
const auto& pixel = decode->pixel;
|
||||
std::vector<DrawProgram> programs = decode->programs;
|
||||
const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) {
|
||||
if (!graphics.stages.mesh) return;
|
||||
auto& words = programs.front().userData;
|
||||
require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words");
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters, programs.front().binary.codeAddress);
|
||||
std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
};
|
||||
if (!drawParameters.indirect) setMeshIndexBuffer(drawParameters);
|
||||
else if (graphics.stages.mesh) setMeshIndexBuffer(Pm4::DrawParameters{drawParameters.indexAddress, std::max(drawParameters.indexCount, 1u), drawParameters.indexSize, 1, 0, drawParameters.indexed});
|
||||
const std::vector<Role>& roles = decode->roles;
|
||||
phase(DrawRowDecode);
|
||||
// The user word an SH register named by an indirect draw packet lands in: the program whose
|
||||
|
|
@ -4297,7 +4306,7 @@ private:
|
|||
mix(graphics.stages.mesh.has_value());
|
||||
if (graphics.stages.mesh) {
|
||||
const auto& mesh = *graphics.stages.mesh;
|
||||
for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex}) mix(value);
|
||||
for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize}) mix(value);
|
||||
}
|
||||
mix(graphics.stages.tessellation.has_value());
|
||||
if (graphics.stages.tessellation) {
|
||||
|
|
@ -4483,7 +4492,7 @@ private:
|
|||
program.binary,
|
||||
{waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory},
|
||||
localDevice->Target(),
|
||||
{0, 0, pushOffset, Graphics::PipelinePushConstantBytes - pushOffset},
|
||||
{0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushConstantBytes) - pushOffset},
|
||||
ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}}
|
||||
};
|
||||
const auto waitedBefore = TraceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
|
||||
|
|
@ -4810,6 +4819,10 @@ private:
|
|||
} else {
|
||||
direct.firstVertex = indirect.indxOffset;
|
||||
}
|
||||
if (graphics.stages.mesh) {
|
||||
setMeshIndexBuffer(direct);
|
||||
patched.insert(0);
|
||||
}
|
||||
for (const auto programIndex : patched) {
|
||||
auto& result = results[resultIndex[programIndex]];
|
||||
const auto pushBytes = result.pushConstants.size();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ namespace AgcDriver::Graphics {
|
|||
// APS5_NO_DRAW_RECIPE=1).
|
||||
void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots = {}, std::shared_ptr<const DrawRecipe>* recipe = nullptr);
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress);
|
||||
|
||||
// Why DrawWithRecipe did not record from the recipe (the caller then runs Draw): the draw is not
|
||||
// recordable (no recorder, APS5_SYNC_DRAWS, APS5_DUMP_TARGETS), a resident target is gone from the
|
||||
// storage cache, the template is gone, the pipeline is gone from the pipeline store, or the
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ inline VkShaderStageFlags PushConstantStages(std::span<const CompiledShader> sha
|
|||
VkShaderStageFlags result = 0;
|
||||
for (const auto& shader : shaders) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
if (!shader.program->pushConstants.empty()) result |= VulkanStage(shader.stage);
|
||||
if (!shader.program->pushConstants.empty() || shader.stage == ShaderRecompiler::ShaderStage::Mesh) result |= VulkanStage(shader.stage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,16 +122,15 @@ struct DrawKeyRange {
|
|||
std::uint32_t first;
|
||||
std::uint32_t count;
|
||||
};
|
||||
inline constexpr std::array<DrawKeyRange, 44> DrawKeyRegisters{{
|
||||
inline constexpr std::array<DrawKeyRange, 45> DrawKeyRegisters{{
|
||||
{RegisterBank::Context, 0x000, 1}, {RegisterBank::Context, 0x002, 1}, {RegisterBank::Context, 0x007, 1}, {RegisterBank::Context, 0x00a, 4}, {RegisterBank::Context, 0x010, 6}, {RegisterBank::Context, 0x01a, 4},
|
||||
{RegisterBank::Context, 0x080, 4}, {RegisterBank::Context, 0x08c, 4}, {RegisterBank::Context, 0x090, 2}, {RegisterBank::Context, 0x094, 2}, {RegisterBank::Context, 0x0b4, 2}, {RegisterBank::Context, 0x105, 4}, {RegisterBank::Context, 0x10b, 3}, {RegisterBank::Context, 0x10f, 6},
|
||||
// SPI_PS_INPUT_CNTL_0..31, SPI_PS_INPUT_ENA/ADDR, SPI_PS_IN_CONTROL, SPI_SHADER_POS/Z/COL_FORMAT,
|
||||
// CB_BLEND0..7_CONTROL, GE_MAX_OUTPUT_PER_SUBGROUP.
|
||||
{RegisterBank::Context, 0x191, 32}, {RegisterBank::Context, 0x1b3, 2}, {RegisterBank::Context, 0x1b6, 1}, {RegisterBank::Context, 0x1c3, 3}, {RegisterBank::Context, 0x1e0, 8}, {RegisterBank::Context, 0x1ff, 1},
|
||||
// DB_DEPTH_CONTROL .. PA_CL_VS_OUT_CNTL, PA_SC_MODE_CNTL_0/1, VGT_GS_MODE, VGT_GS_VERT_ITEMSIZE,
|
||||
// VGT_SHADER_STAGES_EN/GS_ONCHIP, VGT_TF_PARAM/DB_ALPHA_TO_MASK, PA_SC_AA_CONFIG and
|
||||
// PA_SU_VTX_CNTL, the sample masks, PA_SC_CONSERVATIVE_RASTERIZATION_CNTL.
|
||||
{RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1},
|
||||
{RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ab, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1},
|
||||
// CB_COLOR0..7_BASE .. DCC_BASE (15 words a slot), CB_COLOR0..7_BASE_EXT, DCC_BASE_EXT, ATTRIB2, ATTRIB3.
|
||||
{RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x3a8, 0x18},
|
||||
// The pixel program address, RSRC2 and user words; the geometry-back user pointer and program
|
||||
|
|
|
|||
|
|
@ -503,8 +503,10 @@ void CheckBufferAliases(std::span<const CompiledShader> shaders, const ColorTarg
|
|||
const auto address = descriptor.Base48();
|
||||
const auto size = descriptor.GetSize();
|
||||
if (size == 0 || address == 0) continue;
|
||||
const auto element = offset / 4;
|
||||
const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element];
|
||||
Require(!overlap(address, size, target.address, target.bytes), "shader buffer aliases the render target");
|
||||
Require(!overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer");
|
||||
Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -512,6 +514,14 @@ void CheckBufferAliases(std::span<const CompiledShader> shaders, const ColorTarg
|
|||
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress) {
|
||||
const auto address = draw.indexed ? draw.indexAddress : unreadAddress;
|
||||
const auto bytes = draw.indexed ? (static_cast<std::uint64_t>(draw.indexCount) * draw.indexSize + 3u) & ~std::uint64_t{3} : 4u;
|
||||
Require(address != 0 && bytes != 0 && bytes <= 0xffffffffu && (address >> 48u) == 0, "invalid mesh index buffer range");
|
||||
constexpr std::uint32_t RawWord3 = 0x31016facu;
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) & 0xffffu, static_cast<std::uint32_t>(bytes), RawWord3};
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<std::shared_ptr<ShaderResources>>> DrawCopiedWriters() {
|
||||
// Never destroyed: a completion action of a batch still in flight at static teardown may erase from it.
|
||||
static auto* const writers = new std::shared_ptr<std::vector<std::shared_ptr<ShaderResources>>>(std::make_shared<std::vector<std::shared_ptr<ShaderResources>>>());
|
||||
|
|
@ -655,7 +665,6 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
|
|||
APS5_LOG_CHARS_OUT_DEBUG("ValidateShaders OK");
|
||||
APS5_LOG_OUT_DEBUG("PipelineStages=0x%x", static_cast<unsigned>(inputs.shaderStages));
|
||||
if (state.stages.mesh) {
|
||||
Require(draw.firstVertex == 0 && draw.firstInstance == 0, "mesh draw offsets are unsupported");
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Mesh path");
|
||||
Require(context.meshShader, "device does not support mesh shaders");
|
||||
const auto& mesh = *state.stages.mesh;
|
||||
|
|
@ -995,6 +1004,22 @@ struct RecordedDraw {
|
|||
VkShaderStageFlags pushStages = 0;
|
||||
};
|
||||
|
||||
void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, const ShaderResources& resources, const std::array<std::byte, PipelinePushConstantBytes>* bytes, VkShaderStageFlags stages) {
|
||||
auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders);
|
||||
if (bytes == nullptr) {
|
||||
resources.PatchPushConstants(block);
|
||||
stages = PushConstantStages(shaders);
|
||||
}
|
||||
if (!state.stages.mesh) {
|
||||
pipeline.PushConstants(commands, stages, block);
|
||||
return;
|
||||
}
|
||||
const std::array<std::uint32_t, ShaderRecompiler::MeshDrawPushBytes / 4> words{draw.indexCount, draw.firstVertex, draw.firstInstance, draw.indexed ? draw.indexSize : 0u, 0u, 0u};
|
||||
static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushConstantBytes);
|
||||
std::memcpy(block.data() + ShaderRecompiler::MeshDrawPushOffsetBytes, words.data(), sizeof(words));
|
||||
pipeline.PushConstants(commands, stages | VK_SHADER_STAGE_MESH_BIT_EXT, block);
|
||||
}
|
||||
|
||||
// The record of a draw whose targets are all resident (`lean`: recorded into the device's open
|
||||
// batch, rendering in the general layout): one memory barrier before the pass (earlier writes to
|
||||
// the attachments and the draw's inputs visible to it) and one after it, recorded by the recorder
|
||||
|
|
@ -1156,12 +1181,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
|
|||
resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout());
|
||||
}
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Resources bound");
|
||||
if (record.pushBytes != nullptr) record.pipeline->PushConstants(commands, record.pushStages, *record.pushBytes);
|
||||
else {
|
||||
auto bytes = AssemblePushConstants(shaders);
|
||||
resources.PatchPushConstants(bytes);
|
||||
record.pipeline->PushConstants(commands, PushConstantStages(shaders), bytes);
|
||||
}
|
||||
pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages);
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded");
|
||||
recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset);
|
||||
if (args != nullptr) CountIndirectDraw(record.indirect->path, record.indirect->readMs, rewritten);
|
||||
|
|
@ -1580,9 +1600,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK");
|
||||
resources->Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->Layout());
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Resources bound");
|
||||
auto pushBytes = AssemblePushConstants(shaders);
|
||||
resources->PatchPushConstants(pushBytes);
|
||||
pipeline->PushConstants(commands, PushConstantStages(shaders), pushBytes);
|
||||
pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0);
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded");
|
||||
recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset);
|
||||
if (args != nullptr) CountIndirectDraw(indirect.path, indirect.readMs, rewritten);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
if (state.stages.mesh) {
|
||||
Require(context.meshShader, "device does not support VK_EXT_mesh_shader");
|
||||
const auto& mesh = *state.stages.mesh;
|
||||
Require(mesh.threadsPerGroup <= context.meshLimits.maxMeshWorkGroupInvocations && mesh.threadsPerGroup <= context.meshLimits.maxMeshWorkGroupSize[0], "mesh workgroup exceeds device limits");
|
||||
const auto invocations = state.stages.vertexWaveSize == 64u && context.subgroup.subgroupSize == 32u ? mesh.threadsPerGroup / 2u : mesh.threadsPerGroup;
|
||||
Require(invocations <= context.meshLimits.maxMeshWorkGroupInvocations && invocations <= context.meshLimits.maxMeshWorkGroupSize[0], "mesh workgroup exceeds device limits");
|
||||
Require(mesh.maxVertices <= context.meshLimits.maxMeshOutputVertices && mesh.maxPrimitives <= context.meshLimits.maxMeshOutputPrimitives && static_cast<std::uint64_t>(mesh.ldsSizeDwords) * 4 <= context.meshLimits.maxMeshSharedMemorySize, "mesh output or LDS exceeds device limits");
|
||||
}
|
||||
const auto pushStages = PushConstantStages(shaders);
|
||||
|
|
|
|||
|
|
@ -2034,10 +2034,8 @@ std::size_t ShaderResources::addGuestBuffer(std::span<const std::uint32_t> words
|
|||
Require(target == nullptr || !overlap(address, size, target->address, target->bytes), "shader buffer aliases the render target");
|
||||
// APS5_ALL_BUFFERS_WRITTEN=1: every element is noted as written, as before bufferWritten existed.
|
||||
static const bool allWritten = std::getenv("APS5_ALL_BUFFERS_WRITTEN") != nullptr;
|
||||
Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer");
|
||||
written = written || allWritten;
|
||||
// Unconditional (also for read-only elements): Draw.cpp's CheckBufferAliases repeats this check
|
||||
// on every resource-cache hit, and a draw must fail its hit exactly when it fails its build.
|
||||
Require(!overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer");
|
||||
if (written) guestMemory.AddWritable(address, size, atomic);
|
||||
else {
|
||||
guestMemory.AddReadable(address, size);
|
||||
|
|
|
|||
|
|
@ -349,7 +349,9 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
if (mesh) {
|
||||
Require(state.stages.mesh.has_value(), "mesh configuration is missing");
|
||||
const auto& config = *state.stages.mesh;
|
||||
mode(spv::ExecutionModeLocalSize, {config.threadsPerGroup, 1, 1});
|
||||
const auto local = module.modes.find(spv::ExecutionModeLocalSize);
|
||||
const bool paired = state.stages.vertexWaveSize == 64u && subgroup.subgroupSize == 32u && local != module.modes.end() && local->second == std::vector<std::uint32_t>{config.threadsPerGroup / 2u, 1, 1};
|
||||
mode(spv::ExecutionModeLocalSize, {paired ? config.threadsPerGroup / 2u : config.threadsPerGroup, 1, 1});
|
||||
mode(spv::ExecutionModeOutputVertices, {config.maxVertices});
|
||||
mode(spv::ExecutionModeOutputPrimitivesEXT, {config.maxPrimitives});
|
||||
mode(spv::ExecutionModeOutputTrianglesEXT, {});
|
||||
|
|
@ -419,7 +421,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
}
|
||||
}
|
||||
} else if (variable.storage == spv::StorageClassPushConstant) {
|
||||
Require(!push && !shader.pushConstants.empty() && (type[0] & 0xffffu) == spv::OpTypeStruct, "invalid push constant interface");
|
||||
Require(!push && (!shader.pushConstants.empty() || mesh) && (type[0] & 0xffffu) == spv::OpTypeStruct, "invalid push constant interface");
|
||||
push = true;
|
||||
Require(type.size() == 3 && module.decorations[typeId].block, "push constant variable must be a Block struct with exactly one member");
|
||||
const auto offset = module.offsets.find({typeId, 0});
|
||||
|
|
@ -478,7 +480,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
Require(shader.vertexAttributes.empty(), "vertex attribute metadata is invalid for this stage");
|
||||
}
|
||||
Require(descriptors.size() == shader.bindings.size(), "recompiler binding metadata contains undeclared resources");
|
||||
Require(push == !shader.pushConstants.empty(), "recompiler push constant metadata disagrees with SPIR-V");
|
||||
Require(push == (!shader.pushConstants.empty() || mesh), "recompiler push constant metadata disagrees with SPIR-V");
|
||||
for (const auto id : module.interface) Require(module.variables.contains(id), "entry point interface contains an unknown variable");
|
||||
if (mesh) Require(module.primitiveIndices, "mesh shader does not export primitive indices");
|
||||
if (stage == Stage::Vertex || mesh || evaluation) Require(module.position, "vertex shader does not export position");
|
||||
|
|
|
|||
|
|
@ -367,7 +367,10 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
|
|||
validate(groupPrimitives != 0, "geometry subgroup contains no primitives");
|
||||
const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader);
|
||||
validate(((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3, "unsupported geometry VGPR allocation");
|
||||
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((maxVertices + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0};
|
||||
const auto esgsItemSize = read(queue.context, 0x2ab);
|
||||
validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, primitives * (verticesPerPrimitive - 2u)});
|
||||
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,19 @@ add_custom_command(
|
|||
VERBATIM
|
||||
)
|
||||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests)
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/BufferPool.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests)
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_mesh_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/TextureDetilerDescriptors.cpp Graphics/src/TextureCache.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests)
|
||||
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests)
|
||||
if(TARGET ${agcTarget})
|
||||
target_sources(${agcTarget} PRIVATE Graphics/src/GpuColorTransfer.cpp ${agcColorTransferHeader})
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -198,10 +198,15 @@ void ShaderStageTests() {
|
|||
queue.context[0x1ff] = 64;
|
||||
queue.context[0x2ce] = 3;
|
||||
queue.context[0x29b] = 2;
|
||||
queue.context[0x2ab] = 4;
|
||||
queue.shader[0x8a] = 3u << 29u;
|
||||
queue.shader[0x8b] = 3u << 16u;
|
||||
auto stages = AgcDriver::Graphics::DecodeState(queue).stages;
|
||||
Require(stages.path == AgcDriver::Graphics::ShaderPath::Geometry && stages.mesh && stages.mesh->primitivesPerGroup == 21 && stages.mesh->verticesPerGroup == 63, "geometry assembly changed");
|
||||
Require(stages.mesh->maxVertices == 64 && stages.mesh->maxPrimitives == 21 && stages.mesh->threadsPerGroup == 64 && stages.mesh->esgsItemSize == 4, "geometry subgroup outputs changed");
|
||||
queue.context[0x2ab] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
queue.context[0x2ab] = 4;
|
||||
queue.userConfig[0x25b] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid geometry subgroup");
|
||||
queue = makeState();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 192;
|
||||
constexpr std::uint32_t Height = 128;
|
||||
constexpr std::array<std::uint8_t, 4> Background{16, 24, 40, 255};
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 104> GeometryCode{
|
||||
0x8f6a9003, 0x94fe6ac1, 0xbf88000b, 0xd7650006, 0x000100c1, 0xd7660006, 0x00020cc1, 0x93ebff03,
|
||||
0x00040018, 0xd7460006, 0x04190c6b, 0x340c0c82, 0xd8340000, 0x00000506, 0xbf8cc07f, 0xbefe04c1,
|
||||
0xbf8a0000, 0x938dff02, 0x00090016, 0x9382ff03, 0x00040018, 0x938cff03, 0x00080008, 0xd7650009,
|
||||
0x000100c1, 0xd7660009, 0x000212c1, 0xd746000a, 0x04250c02, 0x7da8120c, 0xbf88002e, 0x361600ff,
|
||||
0x0000ffff, 0x2c180090, 0x361a02ff, 0x0000ffff, 0xd8d80000, 0x0b00000b, 0xd8d80000, 0x0c00000c,
|
||||
0xd8d80000, 0x0d00000d, 0xbf8cc07f, 0xe0382000, 0x8002100b, 0xe0382010, 0x8002140b, 0xe0382000,
|
||||
0x8002180c, 0xe0382010, 0x80021c0c, 0xe0382000, 0x8002200d, 0xe0382010, 0x8002240d, 0x161c1483,
|
||||
0x161e14ff, 0x00000060, 0xbf8c3f70, 0xdb7c0400, 0x0000100f, 0xdb7c0410, 0x0000140f, 0xdb7c0420,
|
||||
0x0000180f, 0xdb7c0430, 0x00001c0f, 0xdb7c0440, 0x0000200f, 0xdb7c0450, 0x0000240f, 0x4a501c81,
|
||||
0x4a521c82, 0x3450508a, 0x34525294, 0xd772002a, 0x04a6510e, 0xbf8cc07f, 0xbefe04c1, 0xbf8a0000,
|
||||
0x930e830d, 0xbf078002, 0xbf850003, 0x8f0f8c0d, 0x887c0f0e, 0xbf900009, 0x7da8140d, 0xbf880002,
|
||||
0xf8000941, 0x0000002a, 0xbefe04c1, 0x7da8140e, 0xbf88000a, 0x34561485, 0xdbfc0400, 0x2c00002b,
|
||||
0xdbfc0410, 0x3000002b, 0xbf8cc07f, 0xf80008cf, 0x2f2e2d2c, 0xf800020f, 0x33323130, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
|
||||
0xc8020002, 0xc8060102, 0xc80a0202, 0xc80e0302, 0xf800180f, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
std::array<float, 4> position;
|
||||
std::array<float, 4> color;
|
||||
};
|
||||
|
||||
constexpr std::uint32_t Columns = 8;
|
||||
constexpr std::uint32_t Rows = 5;
|
||||
constexpr std::uint32_t Triangles = Columns * Rows;
|
||||
|
||||
std::array<std::uint8_t, 4> TriangleColor(std::uint32_t triangle) {
|
||||
return {static_cast<std::uint8_t>(40u + 29u * triangle), static_cast<std::uint8_t>(200u + 53u * triangle), static_cast<std::uint8_t>(triangle % 2u == 0u ? 60u : 200u), 255};
|
||||
}
|
||||
|
||||
std::array<Vertex, 3> TriangleVertices(std::uint32_t triangle) {
|
||||
const float cellWidth = 2.0f / Columns;
|
||||
const float cellHeight = 2.0f / Rows;
|
||||
const float x = -1.0f + cellWidth * static_cast<float>(triangle % Columns);
|
||||
const float y = -1.0f + cellHeight * static_cast<float>(triangle / Columns);
|
||||
const auto color = TriangleColor(triangle);
|
||||
const std::array<float, 4> rgba{color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, 1.0f};
|
||||
return {{
|
||||
{{x + 0.1f * cellWidth, y + 0.1f * cellHeight, 0.5f, 1.0f}, rgba},
|
||||
{{x + 0.9f * cellWidth, y + 0.1f * cellHeight, 0.5f, 1.0f}, rgba},
|
||||
{{x + 0.5f * cellWidth, y + 0.9f * cellHeight, 0.5f, 1.0f}, rgba},
|
||||
}};
|
||||
}
|
||||
|
||||
std::size_t CellPixel(std::uint32_t triangle) {
|
||||
const auto column = triangle % Columns;
|
||||
const auto row = triangle / Columns;
|
||||
const auto x = (column * Width + Width / 2u) / Columns;
|
||||
const auto y = Height - 1u - (row * Height + (Height * 4u) / 10u) / Rows;
|
||||
return (static_cast<std::size_t>(y) * Width + x) * 4u;
|
||||
}
|
||||
|
||||
alignas(256) std::array<Vertex, 3 * Triangles> Scrambled{};
|
||||
alignas(256) std::array<std::uint16_t, 3 * Triangles> Indices{};
|
||||
alignas(256) std::array<Vertex, 3 * Triangles> Ordered{};
|
||||
alignas(256) std::array<Vertex, 2 * Columns + 2> Strip{};
|
||||
|
||||
std::array<std::uint32_t, 4> VertexBufferDescriptor(const void* vertices, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(vertices);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (32u << 16u), count, 0x01016facu};
|
||||
}
|
||||
|
||||
void ClearPixels() {
|
||||
for (std::size_t i = 0; i < Pixels.size(); i += 4) {
|
||||
for (std::size_t c = 0; c < 4; ++c) Pixels[i + c] = std::byte{Background[c]};
|
||||
}
|
||||
}
|
||||
|
||||
bool PixelIs(std::size_t offset, const std::array<std::uint8_t, 4>& color) {
|
||||
for (std::size_t c = 0; c < 4; ++c) {
|
||||
if (std::to_integer<std::uint8_t>(Pixels[offset + c]) != color[c]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PixelText(std::size_t offset) {
|
||||
char text[64];
|
||||
std::snprintf(text, sizeof(text), "(%u, %u, %u, %u)", std::to_integer<unsigned>(Pixels[offset]), std::to_integer<unsigned>(Pixels[offset + 1]), std::to_integer<unsigned>(Pixels[offset + 2]), std::to_integer<unsigned>(Pixels[offset + 3]));
|
||||
return text;
|
||||
}
|
||||
|
||||
struct MeshDraw {
|
||||
ShaderRecompiler::MeshConfiguration mesh;
|
||||
AgcDriver::Pm4::DrawParameters draw;
|
||||
std::array<std::uint32_t, 4> vertexBuffer;
|
||||
std::uint32_t geometryPushBytes = ShaderRecompiler::MeshDrawPushOffsetBytes;
|
||||
};
|
||||
|
||||
void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
|
||||
const auto target = device.Target();
|
||||
std::vector<std::uint32_t> userData(12, 0u);
|
||||
const auto index = AgcDriver::Graphics::MeshIndexBufferDescriptor(setup.draw, reinterpret_cast<std::uintptr_t>(GeometryCode.data()));
|
||||
std::copy(index.begin(), index.end(), userData.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
std::copy(setup.vertexBuffer.begin(), setup.vertexBuffer.end(), userData.begin() + 8);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> geometryMemory{{{reinterpret_cast<std::uintptr_t>(GeometryCode.data()), std::as_bytes(std::span(GeometryCode))}}};
|
||||
ShaderRecompiler::RecompileRequest geometry{
|
||||
{ShaderStage::Mesh, reinterpret_cast<std::uintptr_t>(GeometryCode.data()), GeometryCode, 0, {}},
|
||||
{64, 0, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, geometryMemory},
|
||||
target,
|
||||
{0, 0, 0, setup.geometryPushBytes},
|
||||
ShaderRecompiler::GraphicsCompileContext{0, {}, setup.mesh, std::nullopt, {setup.draw.indexAddress, setup.draw.indexCount, setup.draw.indexSize, setup.draw.instanceCount}}
|
||||
};
|
||||
const auto meshResult = ShaderRecompiler::Recompile(geometry);
|
||||
const auto meshPush = static_cast<std::uint32_t>(meshResult.pushConstants.size());
|
||||
Require((meshPush == 0) == (setup.geometryPushBytes == 0), "the geometry program's push data does not follow its layout");
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
pixel.interpolatorCount = 1;
|
||||
pixel.interpolatorSettings[0] = 0x400u;
|
||||
pixel.targetOutputMode[0] = 9;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
|
||||
{64, 0, {}, std::nullopt, pixel, std::nullopt, pixelMemory},
|
||||
target,
|
||||
{0, 0, meshPush, ShaderRecompiler::MeshDrawPushOffsetBytes - meshPush},
|
||||
std::nullopt
|
||||
};
|
||||
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Mesh, &meshResult, 0},
|
||||
{ShaderStage::Fragment, &pixelResult, meshPush}
|
||||
}};
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, 0x20u, 64, 64, setup.mesh, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = setup.mesh.inputPrimitive == 6u ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
device.Draw(state, setup.draw, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
constexpr ShaderRecompiler::MeshConfiguration SmallSubgroup{4u, 4u, 12u, 12u, 4u, 64u, 1024u, 0u, 4u};
|
||||
constexpr ShaderRecompiler::MeshConfiguration WideSubgroup{4u, 32u, 96u, 96u, 32u, 128u, 2048u, 0u, 4u};
|
||||
|
||||
void CheckTriangles(const char* what) {
|
||||
for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) {
|
||||
const auto offset = CellPixel(triangle);
|
||||
Require(PixelIs(offset, TriangleColor(triangle)), std::string(what) + ": triangle " + std::to_string(triangle) + " was not rendered in its color, pixel " + PixelText(offset));
|
||||
}
|
||||
Require(PixelIs(0, Background) && PixelIs(Pixels.size() - 4u, Background), std::string(what) + ": the corners changed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
setenv("APS5_NO_SHADER_DISK_CACHE", "1", 1);
|
||||
for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) {
|
||||
const auto vertices = TriangleVertices(triangle);
|
||||
for (std::uint32_t k = 0; k < 3; ++k) {
|
||||
Ordered[3 * triangle + k] = vertices[k];
|
||||
const auto slot = 3 * (Triangles - 1 - triangle) + (k + 1) % 3;
|
||||
Scrambled[slot] = vertices[k];
|
||||
Indices[3 * triangle + k] = static_cast<std::uint16_t>(slot);
|
||||
}
|
||||
}
|
||||
const std::array<float, 4> stripColor{128.0f / 255.0f, 1.0f, 64.0f / 255.0f, 1.0f};
|
||||
for (std::uint32_t i = 0; i < Strip.size(); ++i) {
|
||||
const float x = -1.0f + 2.0f * static_cast<float>(i / 2u) / Columns;
|
||||
const float y = i % 2u == 0u ? -1.0f : 0.0f;
|
||||
Strip[i] = {{x, y, 0.5f, 1.0f}, stripColor};
|
||||
}
|
||||
|
||||
AgcDriver::VulkanDevice device;
|
||||
const auto target = device.Target();
|
||||
if (!target.mesh.has_value()) {
|
||||
std::puts("Mesh tests skipped: the device has no VK_EXT_mesh_shader");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& [name, subgroup] : {std::pair{"one-wave subgroups", SmallSubgroup}, std::pair{"two-wave subgroups", WideSubgroup}}) {
|
||||
ClearPixels();
|
||||
DrawMesh(device, {subgroup, {reinterpret_cast<std::uintptr_t>(Indices.data()), static_cast<std::uint32_t>(Indices.size()), 2, 1, 0, true}, VertexBufferDescriptor(Scrambled.data(), static_cast<std::uint32_t>(Scrambled.size()))});
|
||||
CheckTriangles((std::string("indexed triangle list, ") + name).c_str());
|
||||
|
||||
ClearPixels();
|
||||
DrawMesh(device, {subgroup, {0, static_cast<std::uint32_t>(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast<std::uint32_t>(Ordered.size()))});
|
||||
CheckTriangles((std::string("non-indexed triangle list, ") + name).c_str());
|
||||
}
|
||||
|
||||
ClearPixels();
|
||||
DrawMesh(device, {SmallSubgroup, {0, static_cast<std::uint32_t>(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast<std::uint32_t>(Ordered.size())), 0});
|
||||
CheckTriangles("mesh program without push data");
|
||||
|
||||
ClearPixels();
|
||||
const ShaderRecompiler::MeshConfiguration strip{6u, 3u, 5u, 9u, 3u, 64u, 1024u, 0u, 4u};
|
||||
DrawMesh(device, {strip, {0, static_cast<std::uint32_t>(Strip.size()), 0, 1, 0, false}, VertexBufferDescriptor(Strip.data(), static_cast<std::uint32_t>(Strip.size()))});
|
||||
const std::array<std::uint8_t, 4> stripPixel{128, 255, 64, 255};
|
||||
for (std::uint32_t y = Height / 2u + 2u; y < Height - 2u; y += 5u) {
|
||||
for (std::uint32_t x = 2u; x < Width - 2u; x += 5u) {
|
||||
const auto offset = (static_cast<std::size_t>(y) * Width + x) * 4u;
|
||||
Require(PixelIs(offset, stripPixel), "triangle strip: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + PixelText(offset));
|
||||
}
|
||||
}
|
||||
Require(PixelIs((static_cast<std::size_t>(Height / 4u) * Width + Width / 2u) * 4u, Background), "triangle strip: the upper half changed");
|
||||
|
||||
std::puts("Mesh tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,12 @@
|
|||
#include "Optimization/RequestMemoryView.hpp"
|
||||
#include "Optimization/ResourceMaterializer.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include "Optimization/ShaderStageInputInfo.hpp"
|
||||
#include "Optimization/SrtWalker/SrtFlatSlotClasses.hpp"
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
#endif
|
||||
#include "CacheKey.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
|
@ -411,6 +413,39 @@ void verifyProgramCounterRelativeData() {
|
|||
require(moved.cacheHit, "program counter data: relocating the shader recompiled it");
|
||||
require(dataBase(moved) == codeAddress + 0x1000u + 56u, "program counter data: the relocated shader bound the old address");
|
||||
}
|
||||
void verifyMeshConfiguration() {
|
||||
using namespace ShaderRecompiler;
|
||||
ShaderMeshInputInfo list;
|
||||
list.inputPrimitive = 4u;
|
||||
require(list.InputPrimitiveSize() == 3u && list.InputPrimitiveStep() == 3u && list.InputVertexCount(21u) == 63u && list.InputPrimitiveCount(63u) == 21u && list.InputPrimitiveCount(2u) == 0u && list.InputVertexCount(0u) == 0u, "triangle list subgroup sizes changed");
|
||||
ShaderMeshInputInfo strip;
|
||||
strip.inputPrimitive = 6u;
|
||||
require(strip.InputPrimitiveSize() == 3u && strip.InputPrimitiveStep() == 1u && strip.InputVertexCount(21u) == 23u && strip.InputPrimitiveCount(23u) == 21u, "triangle strip subgroup sizes changed");
|
||||
ShaderMeshInputInfo lines;
|
||||
lines.inputPrimitive = 2u;
|
||||
ShaderMeshInputInfo points;
|
||||
points.inputPrimitive = 1u;
|
||||
require(lines.InputPrimitiveSize() == 2u && lines.InputPrimitiveStep() == 2u && points.InputPrimitiveSize() == 1u && points.InputVertexCount(5u) == 5u, "line or point subgroup sizes changed");
|
||||
|
||||
static constexpr std::array<std::uint32_t, 1> code{0xbf810000u};
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Mesh, 0x10000u, code, 0, {}};
|
||||
request.context.waveSize = 64;
|
||||
const MeshConfiguration mesh{4u, 21u, 63u, 64u, 21u, 64u, 256u, 0u, 12u};
|
||||
request.graphics = GraphicsCompileContext{0u, {}, mesh, std::nullopt, {}};
|
||||
const auto replay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(request));
|
||||
require(replay.request.graphics.has_value() && replay.request.graphics->mesh.has_value() && replay.request.graphics->mesh->esgsItemSize == 12u && replay.request.graphics->mesh->primitivesPerGroup == 21u, "mesh configuration was lost in serialization");
|
||||
std::vector<std::uint64_t> key;
|
||||
RecompileCacheKey::Build(request, key);
|
||||
const auto first = key;
|
||||
auto other = request;
|
||||
auto otherMesh = mesh;
|
||||
otherMesh.esgsItemSize = 16u;
|
||||
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
|
||||
RecompileCacheKey::Build(other, key);
|
||||
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -420,6 +455,7 @@ int main() {
|
|||
verifyPureFlatSlots();
|
||||
verifyBindlessTable();
|
||||
verifyProgramCounterRelativeData();
|
||||
verifyMeshConfiguration();
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
const std::vector<std::uint32_t> minimalSpirv{
|
||||
0x07230203u, 0x00010000u, 0u, 5u, 0u,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,14 @@ bool Replay(const char* path) {
|
|||
std::printf(" compute: threads %ux%ux%u, lds %u dwords, group ids %d%d%d, tg size %d, thread id components %u\n", compute.numThreads[0], compute.numThreads[1], compute.numThreads[2], compute.ldsSizeDwords, compute.groupIdEnable[0], compute.groupIdEnable[1], compute.groupIdEnable[2], compute.tgSizeEnable, compute.threadIdComponentCount);
|
||||
if (compute.PartialGroups()) std::printf(" partial groups: dispatch of %ux%ux%u threads\n", compute.partialThreads[0], compute.partialThreads[1], compute.partialThreads[2]);
|
||||
}
|
||||
if (request.request.graphics.has_value()) {
|
||||
const auto& graphics = *request.request.graphics;
|
||||
std::printf(" graphics: %zu linked programs, draw %u indices of %u bytes at 0x%llx, %u instances\n", graphics.linkedPrograms.size(), graphics.draw.indexCount, graphics.draw.indexElementBytes, static_cast<unsigned long long>(graphics.draw.indexAddress), graphics.draw.instanceCount);
|
||||
if (graphics.mesh.has_value()) {
|
||||
const auto& mesh = *graphics.mesh;
|
||||
std::printf(" mesh: input primitive %u, %u primitives / %u vertices per group, max %u vertices / %u primitives, %u threads, lds %u dwords, provoking %u, ESGS item %u\n", mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize);
|
||||
}
|
||||
}
|
||||
if (g_memory) {
|
||||
// --mem: the captured inputs. User data words are printed; each memory region is written to
|
||||
// mem_<code address>_<guest address>.bin next to the request for inspection with other tools.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public:
|
|||
append(key, request.context.compute);
|
||||
append(key, request.context.pixel);
|
||||
append(key, request.context.vertex);
|
||||
appendMesh(key, request);
|
||||
append(key, request.target);
|
||||
append(key, DebugProbeActive());
|
||||
}
|
||||
|
|
@ -48,6 +49,7 @@ public:
|
|||
append(key, request.context.compute);
|
||||
append(key, request.context.pixel);
|
||||
append(key, request.context.vertex);
|
||||
appendMesh(key, request);
|
||||
std::uint64_t hash = 0xcbf29ce484222325ull;
|
||||
for (const auto value : key) {
|
||||
hash ^= value;
|
||||
|
|
@ -70,6 +72,14 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
static void appendMesh(std::vector<std::uint64_t>& key, const RecompileRequest& request) {
|
||||
if (request.shader.stage != ShaderStage::Mesh) return;
|
||||
const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr;
|
||||
append(key, mesh != nullptr);
|
||||
if (mesh == nullptr) return;
|
||||
for (const auto value : {mesh->inputPrimitive, mesh->primitivesPerGroup, mesh->verticesPerGroup, mesh->maxVertices, mesh->maxPrimitives, mesh->threadsPerGroup, mesh->ldsSizeDwords, mesh->provokingVertex, mesh->esgsItemSize}) append(key, value);
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
static void append(std::vector<std::uint64_t>& key, TValue value) requires (std::is_integral_v<TValue> || std::is_enum_v<TValue>) {
|
||||
key.push_back(static_cast<std::uint64_t>(value));
|
||||
|
|
|
|||
|
|
@ -426,9 +426,10 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati
|
|||
writer.WriteU32(configuration.threadsPerGroup);
|
||||
writer.WriteU32(configuration.ldsSizeDwords);
|
||||
writer.WriteU32(configuration.provokingVertex);
|
||||
writer.WriteU32(configuration.esgsItemSize);
|
||||
}
|
||||
|
||||
MeshConfiguration readMeshConfiguration(Reader& reader) {
|
||||
MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
|
||||
MeshConfiguration configuration{};
|
||||
configuration.inputPrimitive = reader.ReadU32();
|
||||
configuration.primitivesPerGroup = reader.ReadU32();
|
||||
|
|
@ -438,6 +439,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader) {
|
|||
configuration.threadsPerGroup = reader.ReadU32();
|
||||
configuration.ldsSizeDwords = reader.ReadU32();
|
||||
configuration.provokingVertex = reader.ReadU32();
|
||||
configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u;
|
||||
return configuration;
|
||||
}
|
||||
|
||||
|
|
@ -615,7 +617,7 @@ void writeGraphicsCompileContext(Writer& writer, const GraphicsCompileContext& g
|
|||
writer.WriteU32(graphics.draw.instanceCount);
|
||||
}
|
||||
|
||||
GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGraphicsCompileContext& storage) {
|
||||
GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGraphicsCompileContext& storage, std::uint32_t version) {
|
||||
GraphicsCompileContext graphics{};
|
||||
graphics.firstUserSgpr = reader.ReadU32();
|
||||
const auto programCount = reader.ReadU64();
|
||||
|
|
@ -639,7 +641,7 @@ GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGr
|
|||
}
|
||||
graphics.linkedPrograms = storage.linkedPrograms;
|
||||
if (reader.ReadBool()) {
|
||||
storage.mesh = readMeshConfiguration(reader);
|
||||
storage.mesh = readMeshConfiguration(reader, version);
|
||||
graphics.mesh = storage.mesh;
|
||||
}
|
||||
if (reader.ReadBool()) {
|
||||
|
|
@ -660,7 +662,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
|||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(3u);
|
||||
writer.WriteU32(4u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
|
|
@ -683,7 +685,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
|||
Reader reader(decoded);
|
||||
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
|
||||
const auto version = reader.ReadU32();
|
||||
if (version < 1u || version > 3u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 4u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
DeserializedRequest result{};
|
||||
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
|
||||
result.request.context = readGuestContext(reader, result);
|
||||
|
|
@ -691,7 +693,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
|||
result.request.layout = readBindingLayout(reader);
|
||||
if (reader.ReadBool()) {
|
||||
result.graphicsStorage = std::make_unique<DeserializedGraphicsCompileContext>();
|
||||
result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage);
|
||||
result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage, version);
|
||||
}
|
||||
if (version >= 2u) result.request.useCache = reader.ReadBool();
|
||||
if (version >= 3u && result.request.context.compute.has_value()) {
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ int main(int argc, char** argv) {
|
|||
std::cout << "Disassembly written to disasm.txt (" << disasm.size() << " bytes)\n";
|
||||
|
||||
const ShaderStageKind stageKind = ToShaderStageKind(request.shader.stage);
|
||||
const auto inputInfo = BuildShaderStageInputInfo(stageKind, request.context, request.target.subgroupSize);
|
||||
const auto inputInfo = BuildShaderStageInputInfo(stageKind, request.context, request.target.subgroupSize, request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr);
|
||||
|
||||
GraphBuilder graphBuilder;
|
||||
auto cfg = graphBuilder.Build(decoded);
|
||||
|
|
|
|||
|
|
@ -100,18 +100,19 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
|
|||
std::uint32_t maxVertices = 0;
|
||||
std::uint32_t maxPrimitives = 0;
|
||||
std::uint32_t provokingVertex = 0;
|
||||
std::uint32_t esgsItemSize = 0;
|
||||
|
||||
[[nodiscard]] std::uint32_t InputPrimitiveSize() const {
|
||||
throw std::runtime_error("shader input helper not implemented");
|
||||
return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u;
|
||||
}
|
||||
[[nodiscard]] std::uint32_t InputPrimitiveStep() const {
|
||||
throw std::runtime_error("shader input helper not implemented");
|
||||
return inputPrimitive == 6u ? 1u : InputPrimitiveSize();
|
||||
}
|
||||
[[nodiscard]] std::uint32_t InputPrimitiveCount(std::uint32_t vertices) const {
|
||||
throw std::runtime_error("shader input helper not implemented");
|
||||
return vertices < InputPrimitiveSize() ? 0u : (vertices - InputPrimitiveSize()) / InputPrimitiveStep() + 1u;
|
||||
}
|
||||
[[nodiscard]] std::uint32_t InputVertexCount(std::uint32_t primitives) const {
|
||||
throw std::runtime_error("shader input helper not implemented");
|
||||
return primitives == 0u ? 0u : (primitives - 1u) * InputPrimitiveStep() + InputPrimitiveSize();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -82,11 +82,16 @@ std::uint32_t HostSubgroupSize(const RecompileRequest& request) {
|
|||
return request.target.subgroupSize;
|
||||
}
|
||||
|
||||
ShaderStageInputInfo RequestInputInfo(const RecompileRequest& request) {
|
||||
const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr;
|
||||
return BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request), mesh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IrProgram PrepareResourceProgram(const RecompileRequest& request) {
|
||||
const auto stageKind = toShaderStageKind(request.shader.stage);
|
||||
const auto inputInfo = BuildShaderStageInputInfo(stageKind, request.context, HostSubgroupSize(request));
|
||||
const auto inputInfo = RequestInputInfo(request);
|
||||
|
||||
constexpr RdnaInstructionDecoder decoder;
|
||||
const auto decoded = decoder.Decode(request.shader.code);
|
||||
|
|
@ -278,7 +283,7 @@ std::uint64_t nextVariantId() {
|
|||
}
|
||||
|
||||
CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, const ResourceSnapshot& resourceSnapshot, const ResourceSpecialization& resourceSpecialization) {
|
||||
const auto inputInfo = BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request));
|
||||
const auto inputInfo = RequestInputInfo(request);
|
||||
constexpr DeadCodeEliminator deadCodeEliminator;
|
||||
constexpr ResourceMaterializer resourceMaterializer;
|
||||
resourceMaterializer.Apply(program, resourceSpecialization);
|
||||
|
|
@ -405,7 +410,7 @@ RecompileResult materializeVariant(SourceEntry& source, const RecompileRequest&
|
|||
}
|
||||
|
||||
RecompileResult RecompileImpl(const RecompileRequest& request) {
|
||||
static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request)));
|
||||
static_cast<void>(RequestInputInfo(request));
|
||||
RequestMemoryView memory(request.context.memory);
|
||||
const auto runtime = memory.MakeRuntime(request.context.userData, request.shader.codeAddress);
|
||||
ResourceSnapshot snapshot;
|
||||
|
|
@ -587,7 +592,7 @@ auto recompileReporting(const RecompileRequest& request, Impl&& impl) -> decltyp
|
|||
}
|
||||
|
||||
std::shared_ptr<const IrResourcePlan> GetResourcePlan(const RecompileRequest& request) {
|
||||
static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request)));
|
||||
static_cast<void>(RequestInputInfo(request));
|
||||
if (request.useCache) return getSource(request)->plan;
|
||||
return makeResourcePlan(request);
|
||||
}
|
||||
|
|
@ -616,7 +621,7 @@ std::shared_ptr<const ResourceCapture> CaptureResources(const RecompileRequest&
|
|||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
const auto started = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
// Validates the stage inputs once per request, as GetResourcePlan and Recompile(request) do.
|
||||
static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request)));
|
||||
static_cast<void>(RequestInputInfo(request));
|
||||
auto capture = std::make_shared<ResourceCapture>();
|
||||
if (request.useCache) {
|
||||
capture->source = getSource(request);
|
||||
|
|
@ -631,7 +636,7 @@ std::shared_ptr<const ResourceCapture> CaptureResources(const RecompileRequest&
|
|||
|
||||
std::shared_ptr<const SourceHandle> ResolveSource(const RecompileRequest& request) {
|
||||
if (!request.useCache) return nullptr;
|
||||
static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request)));
|
||||
static_cast<void>(RequestInputInfo(request));
|
||||
return std::make_shared<const SourceHandle>(SourceHandle{getSource(request)});
|
||||
}
|
||||
|
||||
|
|
@ -639,7 +644,7 @@ std::shared_ptr<const ResourceCapture> CaptureResources(const RecompileRequest&
|
|||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
const auto started = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
// The whole vertex family (Vertex, Local, TC, TE, Mesh) validates V# fields the memo key does not cover.
|
||||
if (request.shader.stage != ShaderStage::Compute && request.shader.stage != ShaderStage::Fragment) static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request)));
|
||||
if (request.shader.stage != ShaderStage::Compute && request.shader.stage != ShaderStage::Fragment) static_cast<void>(RequestInputInfo(request));
|
||||
auto capture = std::make_shared<ResourceCapture>();
|
||||
capture->source = handle.source;
|
||||
capture->plan = handle.source->plan;
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ struct MeshConfiguration {
|
|||
std::uint32_t threadsPerGroup;
|
||||
std::uint32_t ldsSizeDwords;
|
||||
std::uint32_t provokingVertex;
|
||||
std::uint32_t esgsItemSize = 0;
|
||||
};
|
||||
|
||||
struct TessellationConfiguration {
|
||||
|
|
@ -184,6 +185,10 @@ struct TessellationConfiguration {
|
|||
std::uint32_t outputTopology;
|
||||
};
|
||||
|
||||
inline constexpr std::uint32_t MeshDrawPushOffsetBytes = 104;
|
||||
inline constexpr std::uint32_t MeshDrawPushBytes = 24;
|
||||
inline constexpr std::uint32_t MeshIndexBufferUserWord = 4;
|
||||
|
||||
struct GraphicsDrawParameters {
|
||||
std::uint64_t indexAddress;
|
||||
std::uint32_t indexCount;
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ void ValidateBdaTarget(const IrProgram& program, const SpirvTargetOptions& targe
|
|||
for (const auto extension : {"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}) {
|
||||
if (std::find(target.supportedExtensions.begin(), target.supportedExtensions.end(), extension) == target.supportedExtensions.end()) throw std::runtime_error(std::string("BDA requires unsupported extension ") + extension);
|
||||
}
|
||||
if (program.Resources().stage == IrShaderStage::Mesh || program.Resources().stage == IrShaderStage::TessellationControl) throw std::runtime_error("BDA fault termination requires a barrier-safe mesh or tessellation-control execution protocol");
|
||||
if (program.Resources().stage == IrShaderStage::TessellationControl) throw std::runtime_error("BDA fault termination requires a barrier-safe tessellation-control execution protocol");
|
||||
}
|
||||
|
||||
bool BdaInvocationsMayStop(const IrProgram& program) {
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Sh
|
|||
state.waveLdsScope = WaveLdsScope(program, workgroup, state.laneCount);
|
||||
if (const char* guard = std::getenv("APS5_LOOP_GUARD")) state.loopGuardLimit = static_cast<std::uint32_t>(std::strtoul(guard, nullptr, 0));
|
||||
// Stopped invocations would leave the wave LDS barriers incomplete.
|
||||
state.bdaStopsInvocations = state.waveLdsScope == 0 && BdaInvocationsMayStop(program);
|
||||
state.bdaStopsInvocations = state.waveLdsScope == 0 && BdaInvocationsMayStop(program) && program.Resources().stage != IrShaderStage::Mesh;
|
||||
EmitModuleHeader(state, bindings);
|
||||
EmitProgram(state);
|
||||
state.module.EmitEntryPoint(ExecutionModelForStage(state.program.Resources().stage), state.mainFunc, "main", state.interfaceVariables);
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ void DefineOutputs(SpirvEmitterState& state) {
|
|||
void DefineDescriptors(SpirvEmitterState& state) {
|
||||
const IrBindingLayout& layout = state.program.Metadata().bindings;
|
||||
const IrShaderStage stage = state.program.Resources().stage;
|
||||
if (layout.UsesPushData()) {
|
||||
if (layout.UsesPushData() || stage == IrShaderStage::Mesh) {
|
||||
const auto type = PushConstantBlockType(state);
|
||||
state.pushConstantVariable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant);
|
||||
state.module.AddName(type, "BufferResource");
|
||||
|
|
|
|||
|
|
@ -532,11 +532,31 @@ void DefineMeshOutputs(SpirvEmitterState& state) {
|
|||
void EmitMeshEntryPoint(SpirvEmitterState& state) {
|
||||
state.module.AddFunction(spv::OpFunction, TypeVoid(state), state.mainFunc, spv::FunctionControlMaskNone, TypeFunction(state));
|
||||
EmitLabel(state, state.module.AllocateId());
|
||||
for (std::uint32_t half = 0; half < state.laneCount; half++) {
|
||||
state.module.AddFunction(spv::OpStore, MeshElement(state, state.meshPrimitiveData, spv::StorageClassPrivate, TypeU32(state), ConstantU32(state, half)), ConstantU32(state, 0x80000000u));
|
||||
}
|
||||
const auto firstInvocation = state.module.AllocateId();
|
||||
state.laneHalf = 0;
|
||||
state.module.AddFunction(spv::OpIEqual, TypeBool(state), firstInvocation, EmitLocalInvocationIndex(state), ConstantU32(state, 0u));
|
||||
EmitIfCondition(state, firstInvocation, [&]() {
|
||||
for (std::uint32_t field = 0; field < 2u; field++) {
|
||||
state.module.AddFunction(spv::OpStore, MeshElement(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, field)), ConstantU32(state, 0u));
|
||||
}
|
||||
});
|
||||
state.module.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.module.AllocateId(), state.meshGuestFunc);
|
||||
state.module.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask));
|
||||
const auto vertices = MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 0u));
|
||||
const auto primitives = MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 1u));
|
||||
const auto& mesh = state.inputInfo.vertex->mesh;
|
||||
const auto clamp = [&](std::uint32_t value, std::uint32_t limit) {
|
||||
const auto result = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpExtInst, TypeU32(state), result, GlslStd450(state), GLSLstd450UMin, value, ConstantU32(state, limit));
|
||||
return result;
|
||||
};
|
||||
const auto vertices = clamp(MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 0u)), mesh.maxVertices);
|
||||
const auto primitives = clamp(MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 1u)), mesh.maxPrimitives);
|
||||
state.module.AddFunction(spv::OpSetMeshOutputsEXT, vertices, primitives);
|
||||
const auto anyVertex = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpExtInst, TypeU32(state), anyVertex, GlslStd450(state), GLSLstd450UMax, vertices, ConstantU32(state, 1u));
|
||||
const auto lastVertex = EmitBinaryU32(state, spv::OpISub, anyVertex, ConstantU32(state, 1u));
|
||||
for (std::uint32_t half = 0; half < state.laneCount; half++) {
|
||||
state.laneHalf = half;
|
||||
const auto index = EmitLocalInvocationIndex(state);
|
||||
|
|
@ -559,8 +579,10 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) {
|
|||
const auto packed = MeshLoad(state, state.meshPrimitiveData, spv::StorageClassPrivate, TypeU32(state), ConstantU32(state, half));
|
||||
std::array<std::uint32_t, 3> vertex {};
|
||||
for (std::uint32_t component = 0; component < 3u; component++) {
|
||||
const auto field = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), field, packed, ConstantU32(state, component * 10u), ConstantU32(state, 9u));
|
||||
vertex.at(component) = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), vertex.at(component), packed, ConstantU32(state, component * 10u), ConstantU32(state, 10u));
|
||||
state.module.AddFunction(spv::OpExtInst, TypeU32(state), vertex.at(component), GlslStd450(state), GLSLstd450UMin, field, lastVertex);
|
||||
}
|
||||
const auto triangle = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3u), triangle, vertex[0], vertex[1], vertex[2]);
|
||||
|
|
@ -937,7 +959,7 @@ std::uint32_t EmitMeshDrawParameter(SpirvValueEmitContext& ctx, const IrValue& i
|
|||
ctx.Fail(inst, "invalid mesh draw parameter");
|
||||
}
|
||||
const auto pointer = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, index));
|
||||
state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, MeshDrawPushOffsetBytes / 4u + index));
|
||||
const auto result = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpLoad, TypeU32(state), result, pointer);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace ShaderRecompiler {
|
|||
|
||||
// hostSubgroupSize is the device subgroup width; wave64 workgroups on 32-wide subgroups run each
|
||||
// invocation as two guest lanes so a guest wave stays one host subgroup.
|
||||
ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize);
|
||||
ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize, const MeshConfiguration* mesh = nullptr);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,12 @@ void TranslationContext::sBarrier() {
|
|||
(void)ir.Emit(IrOpcode::Barrier, IrType::Void, {});
|
||||
}
|
||||
|
||||
void TranslationContext::sSendmsg(const RdnaInstruction&) {
|
||||
void TranslationContext::sSendmsg(const RdnaInstruction& inst) {
|
||||
constexpr std::uint32_t GsAllocReq = 9u;
|
||||
if (program.Resources().stage == IrShaderStage::Mesh && (inst.rawWords[0] & 0xfu) == GsAllocReq) {
|
||||
(void)ir.Emit(IrOpcode::MeshAllocate, IrType::Void, {&ir.GetM0()});
|
||||
return;
|
||||
}
|
||||
(void)ir.Emit(IrOpcode::Sendmsg, IrType::Void, {});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,8 +233,10 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
}
|
||||
} else if (options.stage == ShaderStageKind::Mesh) {
|
||||
const auto& mesh = options.inputInfo.vertex->mesh;
|
||||
if (options.waveSize != 64u || mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup > 64u || totalThreads > 15u * 64u) {
|
||||
throw std::runtime_error("mesh shader translation configuration is not supported");
|
||||
const std::uint32_t size = mesh.InputPrimitiveSize();
|
||||
const std::uint32_t stepCount = mesh.InputPrimitiveStep();
|
||||
if (options.waveSize != 64u || mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % 64u != 0u || totalThreads > 15u * 64u || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) {
|
||||
throw std::runtime_error("mesh shader translation configuration is not supported (wave " + std::to_string(options.waveSize) + ", primitives per group " + std::to_string(mesh.primitivesPerGroup) + ", vertices per group " + std::to_string(mesh.verticesPerGroup) + ", threads " + std::to_string(totalThreads) + ", ESGS item size " + std::to_string(mesh.esgsItemSize) + ")");
|
||||
}
|
||||
constexpr std::uint32_t kTriStripPrimitiveType = 6u;
|
||||
const auto u32 = [&entryIr](std::uint32_t value) -> IrValue& {
|
||||
|
|
@ -250,36 +252,47 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
return entryIr.ISub(lhs, minimum(lhs, rhs));
|
||||
};
|
||||
IrValue& local = builtin(StageInputKind::LocalInvocationIndex);
|
||||
IrValue& primitiveChunk = entryIr.IMul(builtin(StageInputKind::WorkgroupId, 0u), u32(mesh.primitivesPerGroup));
|
||||
IrValue& step = u32(mesh.InputPrimitiveStep());
|
||||
IrValue& size = u32(mesh.InputPrimitiveSize());
|
||||
IrValue& chunk = entryIr.IMul(primitiveChunk, step);
|
||||
IrValue& vertices = minimum(subtractSaturate(draw(0u), chunk), u32(mesh.verticesPerGroup));
|
||||
IrValue& primitives = entryIr.Select(entryIr.ULessThan(vertices, size), u32(0u), entryIr.IAdd(entryIr.Emit(IrOpcode::UDiv32, IrOpcodeType(IrOpcode::UDiv32), {&subtractSaturate(vertices, size), &step}), u32(1u)));
|
||||
IrValue& firstPrimitive = entryIr.IMul(builtin(StageInputKind::WorkgroupId, 0u), u32(mesh.primitivesPerGroup));
|
||||
IrValue& step = u32(stepCount);
|
||||
IrValue& firstVertex = entryIr.IMul(firstPrimitive, step);
|
||||
IrValue& vertices = minimum(subtractSaturate(draw(0u), firstVertex), u32(mesh.verticesPerGroup));
|
||||
IrValue& primitives = entryIr.Select(entryIr.ULessThan(vertices, u32(size)), u32(0u), entryIr.IAdd(entryIr.Emit(IrOpcode::UDiv32, IrOpcodeType(IrOpcode::UDiv32), {&subtractSaturate(vertices, u32(size)), &step}), u32(1u)));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(2), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(vertices, u32(12u)), entryIr.ShiftLeftLogical(primitives, u32(22u))));
|
||||
IrValue& wave = entryIr.ShiftRightLogical(local, u32(6u));
|
||||
IrValue& waveBase = entryIr.BitwiseAnd(local, u32(~63u));
|
||||
IrValue& vertexCount = minimum(subtractSaturate(vertices, waveBase), u32(64u));
|
||||
IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(64u));
|
||||
IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32(((totalThreads + 63u) / 64u) << 28u));
|
||||
IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / 64u) << 28u));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(3), entryIr.BitwiseOr(waveInfo, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(primitiveCount, u32(8u)), vertexCount)));
|
||||
IrValue& parity = mesh.inputPrimitive == kTriStripPrimitiveType ? entryIr.BitwiseAnd(entryIr.IAdd(primitiveChunk, local), u32(1u)) : u32(0u);
|
||||
IrValue& parity = mesh.inputPrimitive == kTriStripPrimitiveType ? entryIr.BitwiseAnd(entryIr.IAdd(firstPrimitive, local), u32(1u)) : u32(0u);
|
||||
IrValue& vertex = entryIr.IMul(local, step);
|
||||
IrValue& first = entryIr.IAdd(vertex, parity);
|
||||
IrValue& second = mesh.InputPrimitiveSize() >= 2u ? entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity) : u32(0u);
|
||||
IrValue& third = mesh.InputPrimitiveSize() == 3u ? entryIr.IAdd(vertex, u32(2u)) : u32(0u);
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(0), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(first, u32(2u)), entryIr.ShiftLeftLogical(second, u32(18u))));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(1), entryIr.ShiftLeftLogical(third, u32(2u)));
|
||||
IrValue& inputVertex = entryIr.IAdd(chunk, local);
|
||||
IrValue& item = u32(mesh.esgsItemSize);
|
||||
IrValue& first = entryIr.IMul(entryIr.IAdd(vertex, parity), item);
|
||||
IrValue& second = size >= 2u ? entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item) : u32(0u);
|
||||
IrValue& third = size == 3u ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : u32(0u);
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(0), entryIr.BitwiseOr(entryIr.BitwiseAnd(first, u32(0xffffu)), entryIr.ShiftLeftLogical(second, u32(16u))));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(1), entryIr.BitwiseAnd(third, u32(0xffffu)));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(2), entryIr.IAdd(firstPrimitive, local));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(3), u32(0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(4), u32(0u));
|
||||
if (options.userDataBaseRegister != 0u || options.userDataCount < 8u) {
|
||||
throw std::runtime_error("mesh shader translation requires the merged program's eight hidden user words");
|
||||
}
|
||||
IrValue& inputVertex = entryIr.IAdd(firstVertex, local);
|
||||
IrValue& indexBytes = draw(3u);
|
||||
IrValue& indexed = entryIr.INotEqual(indexBytes, u32(0u));
|
||||
IrValue& indexLow = draw(4u);
|
||||
IrValue& byteOffset = entryIr.IAdd(entryIr.BitwiseAnd(indexLow, u32(3u)), entryIr.IMul(inputVertex, indexBytes));
|
||||
IrValue& indexResource = entryIr.Emit(IrOpcode::GetAddressResource, IrOpcodeType(IrOpcode::GetAddressResource), {&entryIr.BitwiseAnd(indexLow, u32(~3u)), &draw(5u)});
|
||||
IrValue& byteOffset = entryIr.IMul(inputVertex, indexBytes);
|
||||
IrValue& indexResource = entryIr.Emit(IrOpcode::GetBufferResource, IrOpcodeType(IrOpcode::GetBufferResource), {&entryIr.GetUserData(static_cast<ScalarReg>(4)), &entryIr.GetUserData(static_cast<ScalarReg>(5)), &entryIr.GetUserData(static_cast<ScalarReg>(6)), &entryIr.GetUserData(static_cast<ScalarReg>(7))});
|
||||
const std::uint32_t memoryIndex = static_cast<std::uint32_t>(program.Resources().memoryInfo.size());
|
||||
program.Resources().memoryInfo.push_back(MemoryInfo{.kind = ResourceKind::Global});
|
||||
IrValue& packedIndex = entryIr.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {&indexResource, &entryIr.BitwiseAnd(byteOffset, u32(~3u)), &u32(0u), &entryIr.LogicalAnd(indexed, entryIr.ULessThan(local, vertices))}, MemoryFlags{.index = memoryIndex});
|
||||
program.Resources().memoryInfo.push_back(MemoryInfo{.kind = ResourceKind::Buffer, .resource = 1u, .offen = true});
|
||||
IrValue& packedIndex = entryIr.Emit(IrOpcode::LoadBufferU32, IrOpcodeType(IrOpcode::LoadBufferU32), {&indexResource, &u32(0u), &entryIr.BitwiseAnd(byteOffset, u32(~3u)), &u32(0u), &entryIr.LogicalAnd(indexed, entryIr.ULessThan(local, vertices))}, MemoryFlags{.index = memoryIndex});
|
||||
IrValue& index = entryIr.Emit(IrOpcode::BitFieldUExtract, IrOpcodeType(IrOpcode::BitFieldUExtract), {&packedIndex, &entryIr.IMul(entryIr.BitwiseAnd(byteOffset, u32(3u)), u32(8u)), &entryIr.IMul(indexBytes, u32(8u))});
|
||||
for (std::uint32_t reg = 4u; reg < 8u; reg++) {
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(reg), u32(0u));
|
||||
}
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(5), entryIr.IAdd(draw(1u), entryIr.Select(indexed, index, inputVertex)));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(6), u32(0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(7), u32(0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(8), entryIr.IAdd(draw(2u), builtin(StageInputKind::WorkgroupId, 1u)));
|
||||
} else if (options.stage == ShaderStageKind::Local) {
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(3), entryIr.Constant(64u));
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ void _detectVertexBuffers(ShaderVertexInputInfo& info) {
|
|||
|
||||
}
|
||||
|
||||
ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize) {
|
||||
ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize, const MeshConfiguration* mesh) {
|
||||
switch (stage) {
|
||||
case ShaderStageKind::Compute: {
|
||||
if (!context.compute.has_value()) {
|
||||
|
|
@ -154,6 +154,24 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
|||
vertexStorage.resourcesDst[i].fetchIndex = vertex.resourcesDst[i].fetchIndex;
|
||||
}
|
||||
_detectVertexBuffers(vertexStorage);
|
||||
if (stage == ShaderStageKind::Mesh) {
|
||||
if (mesh == nullptr) throw std::runtime_error("ShaderInputInfoBuilder: a mesh-stage program has no mesh configuration");
|
||||
auto& target = vertexStorage.mesh;
|
||||
target.threadsNum[0] = mesh->threadsPerGroup;
|
||||
target.threadsNum[1] = 1u;
|
||||
target.threadsNum[2] = 1u;
|
||||
target.ldsSizeDwords = mesh->ldsSizeDwords;
|
||||
target.waveSize = context.waveSize;
|
||||
target.hostSubgroupSize = hostSubgroupSize;
|
||||
target.inputPrimitive = mesh->inputPrimitive;
|
||||
target.primitivesPerGroup = mesh->primitivesPerGroup;
|
||||
target.verticesPerGroup = mesh->verticesPerGroup;
|
||||
target.maxVertices = mesh->maxVertices;
|
||||
target.maxPrimitives = mesh->maxPrimitives;
|
||||
target.provokingVertex = mesh->provokingVertex;
|
||||
target.esgsItemSize = mesh->esgsItemSize;
|
||||
if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || target.provokingVertex > 2u) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration");
|
||||
}
|
||||
ShaderStageInputInfo result;
|
||||
result.vertex = &vertexStorage;
|
||||
return result;
|
||||
|
|
|
|||
Loading…
Reference in New Issue