feat(shader): translate NGG geometry programs to mesh shaders
The geometry path's merged ES/GS programs (VGT_SHADER_STAGES_EN.GS_EN) were compiled as mesh-stage programs whose input metadata was never filled in, so every such draw failed with "mesh shader translation configuration is not supported". Recompiler: - BuildShaderStageInputInfo takes the draw's MeshConfiguration; the mesh input helpers (primitive size, step and counts) are implemented. - The prologue emulates one NGG subgroup per workgroup: s2 GS_TG_INFO, s3 the merged wave info per wave, the GS vertex offsets (ES thread index times VGT_ESGS_RING_ITEMSIZE, odd strip triangles swapped), the primitive id, and the ES vertex and instance ids. An indexed draw's vertex id is read from the index buffer through a raw V# the driver puts in hidden user words 4..7. - s_sendmsg GS_ALLOC_REQ becomes MeshAllocate. The epilogue zeroes the allocation first, clamps it to the output limits, culls primitives no thread exported and decodes the 9-bit vertex indices. - The draw parameters live in the last six push dwords. A BDA fault in a mesh program no longer stops the invocation (as for other programs with barriers, the fault is recorded and the read returns zero), so every invocation reaches the subgroup barriers and the epilogue. - The cache keys cover the mesh configuration; requests serialize its new esgsItemSize field (version 4). Driver: - DecodeShaderStages reads VGT_ESGS_RING_ITEMSIZE (now in the draw key) and sizes the subgroup for its ES vertices, GS primitives and outputs. - Stages compile below the mesh draw parameters, which every mesh draw pushes; the mesh stage always takes part in the push range. - The index-buffer V# is set per draw (per record on the CPU-resolved indirect path); the index-buffer alias check spares read-only elements. - The workgroup and LocalSize checks count two guest lanes per invocation on 32-wide subgroups; mesh draws may carry a first vertex or instance. agc_driver_mesh_tests (EXCLUDE_FROM_ALL, needs VK_EXT_mesh_shader) recompiles a hand-assembled merged ES/GS program in the PS5 compiler's layout and draws indexed and non-indexed triangle lists in one- and two-wave subgroups and a triangle strip across odd-starting subgroups, reading the pixels back. agc_shader_replay prints a request's draw and mesh configuration.
This commit is contained in:
parent
fc0596240d
commit
33f9109999
|
|
@ -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)
|
||||
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, {});
|
||||
|
|
|
|||
|
|
@ -363,7 +363,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()
|
||||
|
|
|
|||
|
|
@ -188,10 +188,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,233 @@
|
|||
#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;
|
||||
};
|
||||
|
||||
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, ShaderRecompiler::MeshDrawPushOffsetBytes},
|
||||
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());
|
||||
|
||||
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();
|
||||
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>
|
||||
|
|
@ -405,6 +407,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() {
|
||||
|
|
@ -414,6 +449,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