diff --git a/docs/own/ideas.md b/docs/own/ideas.md index 754f77d4..fd3a37b0 100644 --- a/docs/own/ideas.md +++ b/docs/own/ideas.md @@ -1,6 +1,9 @@ Moved from todo.md. Mostly ideas for experiments that might not even be possible or useful in the end. +- add mode where hooking is disabled. Commands can still be inspected + but, like, just statically. + For shader capture debugging: - can we support capturing images/samples/buffers/RayTracingAccelStructs and other types? Allowed in quite some high-level languages. @@ -28,7 +31,7 @@ For shader capture debugging: don't hook anything. Instead just insert our own command buffers (before and after) into the submission stream where we write out timestamps -- (low prio) add also full batch (e.g. vkQueueSubmit/vkQueueBindSparse) timings? +- (low prio) add also full batch (e.g. vkQueueSubmit/vkQueueBindSparse) timings? Would require custom cb recording on our side - When clicking on a flag, extension name or something, link to vulkan api spec diff --git a/docs/own/todo.md b/docs/own/todo.md index 00f5ba95..6bec0d67 100644 --- a/docs/own/todo.md +++ b/docs/own/todo.md @@ -7,15 +7,12 @@ v0.3: - improve README, add more gifs/pics urgent, bugs: -- [x] look into annoying lmm.cpp:138 match assert - [ ] fix image viewer layout - [ ] viewing texture in command viewer: show size of view (i.e. active mip level), not the texture itself. Can be confusing otherwise - [ ] maybe show full image size on hover? - [ ] also fix mip/layer selector that sometimes automatically resets itself (seen with slice 3D selector e.g. npt surfel lookup tex) -- [ ] finish submissions in order, see CommandHookSubmission::finish - comment for accelStruct captures - [ ] immediately free HookedRecords that are not to be re-used in finish. - [ ] test more on laptop, intel gpu @@ -58,9 +55,7 @@ urgent, bugs: (try to test with RDR2 again) new, workstack: -- [ ] add mode where hooking is disabled. Commands can still be inspected - but, like, just statically. -- [ ] add isStateCmd(const Command&) and remove remaining command dynamic casts +- [ ] use isStateCmd(const Command&) to remove remaining command dynamic casts - [ ] handle imgui cursor-to-be-shown and clipboard - [ ] make sure to pass it via interface - [ ] with hooked overlay, we have to implement it ourselves @@ -244,14 +239,14 @@ Freeze/selection changes: Might also happen when the window is minimized on some platforms? patch capture shader debugging: -- [ ] add support for structs and arrays -- [ ] improve filtering of variables so that it works well for glslang and slang +- [x] add support for structs and arrays +- [x] improve filtering of variables so that it works well for glslang and slang - [x] add support for dispatch thread ID selection - [x] add branch to command patching branch based on root constant? or specialization constant? or just load information dynamically from the capture buffer? - [x] re-add the selection widget (from debug emulation cpp) - - [ ] remove "allow outside of bounds exec" + - [x] remove "allow outside of bounds exec" - [ ] instead allow a mode where just any thread that hits the point writes info (via atomics). Connected to idea below, implementation could be the same. @@ -261,32 +256,42 @@ patch capture shader debugging: breakpoint was even hit. Clear it to 0 before recording dst command - [x] when not hit: do not show variables in output, instead state that breakpoint is not hit. -- [ ] add more general widget for selecting a stage of a pipe in the debugger -- [ ] add support for vertex shader debugging +- [x] add more general widget for selecting a stage of a pipe in the debugger +- [x] add support for vertex shader debugging - [x] branch based on vertexID - [x] widget to select vertexID. See vertexViewer branch -- [ ] additional shader debug selects - - [ ] Layer - - [ ] ViewIndex - - [ ] ViewportIndex - [ ] add support for pixel shader debugging - - [ ] branch based on pixel position + - [x] branch based on pixel position - [ ] allow to select sample for msaa - - [ ] widget to select pixel position + - [x] widget to select pixel position + - [x] alternative positions: mouse cursor/last cliked mouse cursor - [ ] allow to get there via a "debug this pixel" button in image viewer? +- [ ] separate function arguments and local vars in UI +- [ ] toggle via UI: also capture all local named SSA IDs +- [ ] matrix decoration in captured output +- [ ] show global variables in captured output? (entry point interface vars) + - [ ] also builtins? maybe in different tab/node? - [ ] ray tracing debugging - - [ ] branch in all shaders via LaunchID + - [x] branch in all shaders via LaunchID - [ ] additionally: allow to branch via ahs/chs inputs - [ ] hook shader tables create reverse mapping inside of pipeline then, when hooking the traceRayCommand, create own shader table on the fly where the hooked shaders are replaced? -- [ ] separate function arguments and local vars in UI -- [ ] toggle via UI: also capture all local named SSA IDs -- [ ] matrix decoration in captured output -- [ ] show global variables in captured output? - - [ ] also builtins? maybe in different tab/node? +- [ ] fix CRASH potential in PatchJobData::pipe. Should not be + IntrusiveDerivedPtr, something else. Should keep it alive in + a different way. +- [ ] fix terrible pipeline-keepAlive ShaderPatch hack +- [ ] additional shader debug selects (vertex/fragment) + - [ ] Layer + - [ ] ViewIndex + - [ ] ViewportIndex +- [ ] improve patch compile time by passing in basePipelineHandle +- [ ] improve patching speed: don't insert every instruction into spirv vector. + First gather everything (for every section etc) then do one + patch-build pass. + Current approach copies again and again, problematic for large shaders. spvm: - [x] Add OpSpecConstant* support diff --git a/src/command/commands.hpp b/src/command/commands.hpp index dc2cdabf..7296cf77 100644 --- a/src/command/commands.hpp +++ b/src/command/commands.hpp @@ -472,6 +472,12 @@ struct StateCmdBase : Command { void visit(CommandVisitor& v) const override { doVisit(v, *this); } }; +inline bool isStateCmd(const Command& cmd) { + return cmd.category() == CommandCategory::dispatch || + cmd.category() == CommandCategory::draw || + cmd.category() == CommandCategory::traceRays; +} + struct DrawCmdBase : StateCmdBase { const GraphicsState* state {}; PushConstantData pushConstants; diff --git a/src/commandHook/record.cpp b/src/commandHook/record.cpp index c1ad14c7..48ce615f 100644 --- a/src/commandHook/record.cpp +++ b/src/commandHook/record.cpp @@ -464,8 +464,9 @@ void CommandHookRecord::hookRecordDst(Command& cmd, RecordInfo& info) { dev.dispatch.CmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, info.ops.useCapturePipe); } else if(cmd.category() == CommandCategory::traceRays) { - dev.dispatch.CmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, - info.ops.useCapturePipe); + dlg_warn("TODO: implement capturePipe for RayTracing via shaderTable patching"); + // dev.dispatch.CmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, + // info.ops.useCapturePipe); } else { dlg_error("Unexpected hooked command used with capturePipe"); } @@ -1732,11 +1733,14 @@ void CommandHookRecord::afterDstOutsideRp(Command& bcmd, RecordInfo& info) { if(info.ops.useCapturePipe) { if(bcmd.category() == CommandCategory::draw) { - dlg_error("rebindGraphicsState not implemented"); + dev.dispatch.CmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, + static_cast(bcmd).boundPipe()->handle); } else if(bcmd.category() == CommandCategory::dispatch) { - info.rebindComputeState = true; + dev.dispatch.CmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, + static_cast(bcmd).boundPipe()->handle); } else if(bcmd.category() == CommandCategory::traceRays) { - dlg_error("rebindRayTraceState not implemented"); + dev.dispatch.CmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, + static_cast(bcmd).boundPipe()->handle); } else { dlg_error("Unexpected hooked command used with capturePipe"); } diff --git a/src/data/shaderTable.comp b/src/data/shaderTable.comp index b6e3b3ae..94ffeaf3 100644 --- a/src/data/shaderTable.comp +++ b/src/data/shaderTable.comp @@ -1,5 +1,6 @@ #version 460 +#extension GL_EXT_buffer_reference : require #extension GL_GOOGLE_include_directive : require layout(local_size_x = 64) in; @@ -17,12 +18,12 @@ layout(buffer_reference, std430, buffer_reference_align = 16) buffer Mappings { }; layout(push_constant) uniform PCR { - uint stride; - uint size; - uint count; Src src; Dst dst; Mappings mappings; + uint stride; + uint size; + uint count; } pcr; void main() { diff --git a/src/device.cpp b/src/device.cpp index 5aa3e608..02a44476 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -569,6 +569,10 @@ VkResult doCreateDevice( fpPhdevFeatures2 && fpPhdevProps2 && ((ini.vulkan12 && phdevProps.apiVersion >= VK_API_VERSION_1_2) || hasExt(supportedExts, VK_KHR_8BIT_STORAGE_EXTENSION_NAME)); + auto hasDrawParamsApi = + fpPhdevFeatures2 && fpPhdevProps2 && + ((ini.vulkan11 && phdevProps.apiVersion >= VK_API_VERSION_1_1) || + hasExt(supportedExts, VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME)); auto hasDeviceFaultApi = enableDeviceFault && fpPhdevFeatures2 && fpPhdevProps2 && hasExt(supportedExts, VK_EXT_DEVICE_FAULT_EXTENSION_NAME); @@ -583,6 +587,7 @@ VkResult doCreateDevice( auto hasAddressBindingReport = false; bool hasStorage8 = false; bool hasStorage16 = false; + bool hasDrawParams = false; // find generally relevant feature structs in chain VkPhysicalDeviceVulkan11Features features11 {}; @@ -598,6 +603,7 @@ VkResult doCreateDevice( VkPhysicalDeviceAddressBindingReportFeaturesEXT* inABR = nullptr; VkPhysicalDevice16BitStorageFeatures* inStorage16 {}; VkPhysicalDevice8BitStorageFeatures* inStorage8 {}; + VkPhysicalDeviceShaderDrawParametersFeatures* inDrawParams {}; auto* link = static_cast(pNext); while(link) { @@ -626,6 +632,8 @@ VkResult doCreateDevice( inStorage8 = reinterpret_cast(link); } else if(link->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES) { inStorage16 = reinterpret_cast(link); + } else if(link->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES) { + inDrawParams = reinterpret_cast(link); } link = (static_cast(link->pNext)); @@ -637,8 +645,8 @@ VkResult doCreateDevice( VkPhysicalDeviceAddressBindingReportFeaturesEXT abrFeatures {}; VkPhysicalDevice16BitStorageFeatures storage16Features {}; VkPhysicalDevice8BitStorageFeatures storage8Features {}; - if(hasTimelineSemaphoresApi || hasTransformFeedbackApi || hasDeviceFaultApi || - has16BitStorageApi || has8BitStorageApi) { + VkPhysicalDeviceShaderDrawParametersFeatures drawParamsFeatures {}; + if(fpPhdevFeatures2 && fpPhdevProps2) { dlg_assert(fpPhdevFeatures2); dlg_assert(fpPhdevProps2); @@ -669,6 +677,12 @@ VkResult doCreateDevice( features2.pNext = &storage16Features; } + if(hasDrawParamsApi) { + drawParamsFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + drawParamsFeatures.pNext = features2.pNext; + features2.pNext = &drawParamsFeatures; + } + if(hasDeviceFaultApi) { dfFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT; dfFeatures.pNext = features2.pNext; @@ -749,6 +763,19 @@ VkResult doCreateDevice( } } + if(drawParamsFeatures.shaderDrawParameters) { + hasDrawParams = true; + + if(inDrawParams) { + inDrawParams->shaderDrawParameters = true; + } else if(inVulkan11) { + inVulkan11->shaderDrawParameters = true; + } else { + drawParamsFeatures.pNext = const_cast(nci.pNext); + nci.pNext = &drawParamsFeatures; + } + } + if(dfFeatures.deviceFault) { hasDeviceFault = true; @@ -845,6 +872,7 @@ VkResult doCreateDevice( dev.extDeviceFault = hasDeviceFault; dev.storage8Bit = hasStorage8; dev.storage16Bit = hasStorage16; + dev.shaderDrawParameters = hasDrawParams; if(hasAddressBindingReport) { dev.addressMap = std::make_unique(); diff --git a/src/device.hpp b/src/device.hpp index e0653cb1..125391b4 100644 --- a/src/device.hpp +++ b/src/device.hpp @@ -98,6 +98,7 @@ struct Device { bool storage8Bit {}; bool storage16Bit {}; bool shaderStorageImageWriteWithoutFormat {}; + bool shaderDrawParameters {}; bool extDeviceFault {}; // whether EXT_device_fault was enabled // Only valid when EXT_device_address_binding_report enabled. diff --git a/src/gui/command.cpp b/src/gui/command.cpp index 9662b5f0..fa34e44f 100644 --- a/src/gui/command.cpp +++ b/src/gui/command.cpp @@ -1539,49 +1539,13 @@ void CommandViewer::displayCommand() { } } - // TODO: WIP - if(command_.back()->category() == CommandCategory::dispatch) { - auto* dcmd = deriveCast(command_.back()); - if(ImGui::Button("Debug shader")) { - if(dcmd->state->pipe) { - auto mod = copySpecializeSpirv(dcmd->state->pipe->stage); - shaderDebugger_.select(VK_SHADER_STAGE_COMPUTE_BIT, - std::move(mod), dcmd->state->pipe->stage.entryPoint); - view_ = IOView::shader; - doUpdateHook_ = true; - return; - } - } - } else if(auto* dcmd = dynamic_cast(command_.back()); dcmd) { - auto* pipe = dcmd->boundPipe(); - if(pipe) { - dlg_assert(pipe->type == VK_PIPELINE_BIND_POINT_GRAPHICS); - auto& gpipe = static_cast(*pipe); - const PipelineShaderStage* vertex {}; - for(auto& stage : gpipe.stages) { - if(stage.stage == VK_SHADER_STAGE_VERTEX_BIT) { - vertex = &stage; - break; - } - } - - // TODO: add support for mesh shader. Or at least show - // message in UI? - dlg_assertm(vertex, "TODO Graphics pipeline without vertex shader"); - if(vertex) { - if(ImGui::Button("Debug vertex shader")) { - if(dcmd->state->pipe) { - auto mod = copySpecializeSpirv(*vertex); - shaderDebugger_.select(vertex->stage, std::move(mod), - vertex->entryPoint); - view_ = IOView::shader; - doUpdateHook_ = true; - return; - } - } - } - - // TODO: add fragment shader + if(isStateCmd(*command_.back())) { + auto* dcmd = deriveCast(command_.back()); + if(dcmd->boundPipe() && ImGui::Button("Debug shader")) { + shaderDebugger_.select(*dcmd->boundPipe()); + view_ = IOView::shader; + doUpdateHook_ = true; + return; } } diff --git a/src/gui/shader.cpp b/src/gui/shader.cpp index 59cc34fb..0f6fc841 100644 --- a/src/gui/shader.cpp +++ b/src/gui/shader.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,55 @@ void ShaderDebugger::init(Gui& gui) { textedit_.SetReadOnly(true); } +void ShaderDebugger::select(const Pipeline& pipe) { + if(pipe.type == VK_PIPELINE_BIND_POINT_COMPUTE) { + auto& cpipe = static_cast(pipe); + auto mod = copySpecializeSpirv(cpipe.stage); + select(VK_SHADER_STAGE_COMPUTE_BIT, + std::move(mod), cpipe.stage.entryPoint); + } else if(pipe.type == VK_PIPELINE_BIND_POINT_GRAPHICS) { + auto& gpipe = static_cast(pipe); + const PipelineShaderStage* fragment {}; + const PipelineShaderStage* vertex {}; + for(auto& stage : gpipe.stages) { + if(stage.stage == VK_SHADER_STAGE_VERTEX_BIT) { + vertex = &stage; + } else if(stage.stage == VK_SHADER_STAGE_FRAGMENT_BIT) { + fragment = &stage; + } + } + + if(vertex) { + auto mod = copySpecializeSpirv(*vertex); + select(vertex->stage, std::move(mod), vertex->entryPoint); + } else if(fragment) { + auto mod = copySpecializeSpirv(*fragment); + select(fragment->stage, std::move(mod), fragment->entryPoint); + } else { + dlg_error("No supported debugging stage in pipeline"); + unselect(); + } + } else if(pipe.type == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) { + auto& rpipe = static_cast(pipe); + const PipelineShaderStage* raygen {}; + for(auto& stage : rpipe.stages) { + if(stage.stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { + raygen = &stage; + break; + } + } + + if(!raygen) { + dlg_error("No raygen shader in raytracing pipe?!"); + unselect(); + return; + } + + auto mod = copySpecializeSpirv(*raygen); + select(raygen->stage, std::move(mod), raygen->entryPoint); + } +} + void ShaderDebugger::select(VkShaderStageFlagBits stage, std::unique_ptr compiled, std::string entryPoint) { @@ -82,11 +132,9 @@ void ShaderDebugger::unselect() { currentFile_ = 0u; sourceFilesIDs_.clear(); entryPoint_ = {}; + invocationID_ = {}; - globalInvocationID_ = {}; - commandID_ = {}; - vertexID_ = {}; - instanceID_ = {}; + patch_.reset(gui_->dev()); } void ShaderDebugger::draw() { @@ -95,11 +143,45 @@ void ShaderDebugger::draw() { return; } - auto hookState = selection().completedHookState(); + auto* baseCmd = selection().command().back(); + auto* stateCmd = deriveCast(baseCmd); + dlg_assert(stateCmd->boundPipe()); + + if(stateCmd->category() == CommandCategory::draw) { + auto* pipe = static_cast(stateCmd->boundPipe()); + + if(ImGui::BeginCombo("Shader Stage", vk::name(stage_))) { + for(auto& stage : pipe->stages) { + if(stage.stage != VK_SHADER_STAGE_VERTEX_BIT && + stage.stage != VK_SHADER_STAGE_FRAGMENT_BIT) { + dlg_warn("shader stage {} unsupported for debugging", + vk::name(stage.stage)); + continue; + } - if(!hookState) { - imGuiText("Waiting for submission using the shader..."); - return; + auto str = dlg::format("{}: {}", vk::name(stage.stage), stage.entryPoint); + if(ImGui::Selectable(str.c_str())) { + auto mod = copySpecializeSpirv(stage); + select(stage.stage, std::move(mod), stage.entryPoint); + break; + } + } + ImGui::EndCombo(); + } + } else if(stateCmd->category() == CommandCategory::traceRays) { + auto* pipe = static_cast(stateCmd->boundPipe()); + + if(ImGui::BeginCombo("Shader Stage", vk::name(stage_))) { + for(auto& stage : pipe->stages) { + auto str = dlg::format("{}: {}", vk::name(stage.stage), stage.entryPoint); + if(ImGui::Selectable(str.c_str())) { + auto mod = copySpecializeSpirv(stage); + select(stage.stage, std::move(mod), stage.entryPoint); + break; + } + } + ImGui::EndCombo(); + } } if(ImGui::BeginCombo("File", fileName(currentFile_).c_str())) { @@ -122,7 +204,9 @@ void ShaderDebugger::draw() { if(livePatch_) { auto& dev = gui_->dev(); - patch_.updateJobs(dev); + if(patch_.updateJobs(dev)) { + updateHooks(*dev.commandHook); + } auto currLine = 1 + u32(textedit_.GetCursorPosition().mLine); if(currLine != patch_.line || currentFile_ != patch_.file) { @@ -131,9 +215,6 @@ void ShaderDebugger::draw() { patch_.line = currLine; patch_.file = currentFile_; - auto* baseCmd = selection().command().back(); - auto* stateCmd = deriveCast(baseCmd); - dlg_assert(stateCmd->boundPipe()); auto& job = patch_.jobs.emplace_back(); job.data = std::make_unique(); @@ -144,12 +225,6 @@ void ShaderDebugger::draw() { job.data->file = patch_.file; job.data->line = patch_.line; job.data->captureAddress = dev.commandHook->shaderCaptureAddress(); - // TODO: technically we have a race condition here (no data race tho). - // when the user changes the invocation id in the ui before - // this job finishes, it might get reset by the hook installed - // by this job. Could fix it by checking for this condition - // when retrieving the future and updating the hook. - job.data->captureInput = globalInvocationID_; dlg_debug("Starting new patch job for {}:{}", patch_.file, patch_.line); job.result = std::async(std::launch::async, @@ -159,6 +234,19 @@ void ShaderDebugger::draw() { ImGui::EndChild(); + auto prevID = invocationID_; + if(stage_ == VK_SHADER_STAGE_FRAGMENT_BIT) { + auto& io = ImGui::GetIO(); + if(fragmentMode_ == FragmentMode::cursor) { + invocationID_.x = io.MousePos[0]; + invocationID_.y = io.MousePos[1]; + } else if(fragmentMode_ == FragmentMode::cursorClicked && + io.MouseClicked[1]) { + invocationID_.x = io.MousePos[0]; + invocationID_.y = io.MousePos[1]; + } + } + // variable view if(ImGui::BeginChild("Views")) { if(ImGui::BeginTabBar("Tabs")) { @@ -189,6 +277,11 @@ void ShaderDebugger::draw() { } } + auto changed = prevID != invocationID_; + if(changed && patch_.current.pipe.vkHandle()) { + updateHooks(*gui_->dev().commandHook); + } + ImGui::EndChild(); } @@ -362,34 +455,90 @@ void ShaderDebugger::drawInputsTab() { drawInputsCompute(); } else if(stage_ == VK_SHADER_STAGE_VERTEX_BIT) { drawInputsVertex(); + } else if(stage_ == VK_SHADER_STAGE_FRAGMENT_BIT) { + drawInputsFragment(); } else { dlg_error("Unsupported stage: {}", stage_); imGuiText("Error: unsupported stage selected"); } } +const char* ShaderDebugger::name(FragmentMode mode) { + switch(mode) { + case FragmentMode::cursor: + return "Cursor"; + case FragmentMode::cursorClicked: + return "Clicked Cursor"; + case FragmentMode::none: + return "None"; + case FragmentMode::count: + dlg_error("unreachable"); + return ""; + } + + return ""; +} + +void ShaderDebugger::drawInputsFragment() { + // TODO: only show this when rendering via overlay, does not + // make sense in separate window. We do not have this info + // here atm tho + + if(ImGui::BeginCombo("Debug From", name(fragmentMode_))) { + for(auto mode = u32(FragmentMode::none); mode < u32(FragmentMode::count); ++mode) { + if(ImGui::Selectable(name(FragmentMode(mode)))) { + fragmentMode_ = FragmentMode(mode); + } + } + ImGui::EndCombo(); + } + + ImGui::DragInt2("Position", (int*) &invocationID_); +} + void ShaderDebugger::drawInputsVertex() { + auto& commandID = invocationID_[0]; + auto& instanceID = invocationID_[1]; + auto& vertexID = invocationID_[2]; + auto [numCmds, indexed] = drawInfo(); - auto sliderFlags = ImGuiSliderFlags_AlwaysClamp; + auto sliderFlags = 0u; + + // commandID if(numCmds > 1) { - auto v = int(commandID_); - ImGui::DragInt("Command", &v, 1.f, 0, numCmds - 1, "%d", sliderFlags); - commandID_ = v; + if(gui_->dev().shaderDrawParameters) { + auto v = int(commandID); + ImGui::DragInt("Command", &v, 1.f, 0, numCmds - 1, "%d", sliderFlags); + commandID = v; + } else { + commandID = 0u; + dlg_error("Multiple draw commands but device does not have " + "ShaderParameters capability"); + } } + commandID = std::min(commandID, numCmds - 1); - auto drawCmd = drawCmdInfo(commandID_); - if(drawCmd.firstIni > 1) { - auto v = int(instanceID_); + // instanceID + auto drawCmd = drawCmdInfo(commandID); + if(drawCmd.numInis > 1) { + auto v = int(instanceID); ImGui::DragInt("Instance", &v, 1.f, drawCmd.firstIni, drawCmd.firstIni + drawCmd.numInis - 1, "%d", sliderFlags); - instanceID_ = v; + instanceID = v; } - - auto v = int(vertexID_); - ImGui::DragInt("ID", &v, 1.f, 0, drawCmd.numVerts - 1, "%d", sliderFlags); - // TODO: show popup explaining this is the vertex ID, i.e. for indexed - // drawing the index to be read. - vertexID_ = v; + instanceID = std::min(instanceID, drawCmd.numInis - 1); + + // vertexID + auto v = int(vertexID); + auto minVertID = 0u; + auto maxVertID = 0x7FFFFFFFu; + if(!indexed) { + minVertID = drawCmd.vertexOffset; + maxVertID = minVertID + drawCmd.numVerts; + } + ImGui::DragInt("ID", &v, 1.f, minVertID, maxVertID - 1, "%d", + ImGuiSliderFlags_AlwaysClamp); + vertexID = v; } void ShaderDebugger::drawInputsCompute() { @@ -404,9 +553,9 @@ void ShaderDebugger::drawInputsCompute() { imGuiText("This dispatch call has no invocations!"); } else { auto sliderFlags = 0u; - globalInvocationID_.x = std::min(globalInvocationID_.x, numThreads.x - 1); - globalInvocationID_.y = std::min(globalInvocationID_.y, numThreads.y - 1); - globalInvocationID_.z = std::min(globalInvocationID_.z, numThreads.z - 1); + invocationID_.x = std::min(invocationID_.x, numThreads.x - 1); + invocationID_.y = std::min(invocationID_.y, numThreads.y - 1); + invocationID_.z = std::min(invocationID_.z, numThreads.z - 1); sliderFlags = ImGuiSliderFlags_AlwaysClamp; // output global @@ -434,9 +583,9 @@ void ShaderDebugger::drawInputsCompute() { } ImGui::PushItemWidth(sizeX); - int v = globalInvocationID_[i]; + int v = invocationID_[i]; changed |= ImGui::DragInt("", &v, 1.f, 0, numThreads[i] - 1, "%d", sliderFlags); - globalInvocationID_[i] = u32(v); + invocationID_[i] = u32(v); ImGui::PopID(); } @@ -461,10 +610,10 @@ void ShaderDebugger::drawInputsCompute() { ImGui::PushItemWidth(sizeX); // floor by design - auto before = globalInvocationID_[i] / wgs[i]; + auto before = invocationID_[i] / wgs[i]; int v = before; if(ImGui::DragInt("", &v, 1.f, 0, numWGs[i] - 1, "%d", sliderFlags)) { - globalInvocationID_[i] += (v - before) * wgs[i]; + invocationID_[i] += (v - before) * wgs[i]; changed = true; } ImGui::PopID(); @@ -491,10 +640,10 @@ void ShaderDebugger::drawInputsCompute() { ImGui::PushItemWidth(sizeX); // floor by design - auto before = globalInvocationID_[i] % wgs[i]; + auto before = invocationID_[i] % wgs[i]; int v = before; if(ImGui::DragInt("", &v, 1.f, 0, wgs[i] - 1, "%d", sliderFlags)) { - globalInvocationID_[i] += v - before; + invocationID_[i] += v - before; changed = true; } ImGui::PopID(); @@ -504,9 +653,7 @@ void ShaderDebugger::drawInputsCompute() { } } - if(changed && patch_.current.pipe.vkHandle()) { - updateHooks(*gui_->dev().commandHook); - } + (void) changed; } void ShaderDebugger::updateHooks(CommandHook& hook) { @@ -514,11 +661,12 @@ void ShaderDebugger::updateHooks(CommandHook& hook) { hookUpdate.invalidate = true; auto& ops = hookUpdate.newOps.emplace(); ops.useCapturePipe = patch_.current.pipe.vkHandle(); - ops.capturePipeInput = Vec3u32(globalInvocationID_); + ops.capturePipeInput = Vec3u32(invocationID_); hook.updateHook(std::move(hookUpdate)); } -void ShaderDebugPatch::updateJobs(Device& dev) { +bool ShaderDebugPatch::updateJobs(Device& dev) { + auto finished = false; std::lock_guard lock(dev.mutex); for(auto it = jobs.begin(); it != jobs.end();) { @@ -538,6 +686,7 @@ void ShaderDebugPatch::updateJobs(Device& dev) { if(res.error.empty()) { dlg_debug("patch job has finished succesfully. {} captures", res.captures.size()); + finished = true; } else { dlg_error("patch job error: {}", res.error); } @@ -547,23 +696,14 @@ void ShaderDebugPatch::updateJobs(Device& dev) { it = jobs.erase(it); } + + return finished; } void ShaderDebugPatch::reset(Device& dev) { for(auto it = jobs.begin(); it != jobs.end();) { auto& job = *it; - auto state = [&]{ - std::lock_guard lock(dev.mutex); - return job.data->state; - }(); - - if(state == PatchJobState::installing) { - job.result.get(); - it = jobs.erase(it); - continue; - } - { std::lock_guard lock(dev.mutex); job.data->state = PatchJobState::canceled; diff --git a/src/gui/shader.hpp b/src/gui/shader.hpp index 4eea528f..c6c6d6ba 100644 --- a/src/gui/shader.hpp +++ b/src/gui/shader.hpp @@ -31,7 +31,7 @@ struct ShaderDebugPatch { // TODO: find proper solution std::vector keepAlive; - void updateJobs(Device& dev); + bool updateJobs(Device& dev); void reset(Device& dev); }; @@ -41,6 +41,7 @@ class ShaderDebugger { ~ShaderDebugger(); void init(Gui& gui); + void select(const Pipeline& pipe); // Select takes its own copy of a spc::Compiler mainly // because of the specialization constant problematic void select(VkShaderStageFlagBits stage, @@ -54,7 +55,7 @@ class ShaderDebugger { Vec3ui numWorkgroups() const; const auto& breakpoints() const { return breakpoints_; } const auto& compiled() const { return compiled_; } - const auto& globalInvocationID() const { return globalInvocationID_; } + const auto& invocationID() const { return invocationID_; } CommandSelection& selection() const; void updateHooks(CommandHook&); @@ -62,7 +63,7 @@ class ShaderDebugger { // vertex stuff struct DrawCmdInfo { i32 vertexOffset; // for non-indexed this is 'firstVertex' - u32 numVerts; // for indexed, this is 'instanceCount' + u32 numVerts; // for indexed, this is 'indexCount' u32 numInis; u32 firstIni; u32 firstIndex {}; // only for indexed draw @@ -73,6 +74,15 @@ class ShaderDebugger { bool indexed {}; }; + enum class FragmentMode { + none, + cursor, + cursorClicked, + count, + }; + + const char* name(FragmentMode); + DrawInfo drawInfo() const; DrawCmdInfo drawCmdInfo(u32 cmd = 0u) const; @@ -81,6 +91,7 @@ class ShaderDebugger { void drawInputsTab(); void drawInputsCompute(); void drawInputsVertex(); + void drawInputsFragment(); std::string_view fileContents(u32 fileID); @@ -110,11 +121,8 @@ class ShaderDebugger { std::vector breakpoints_; std::vector sourceFilesIDs_; u32 currentFile_ {}; - - Vec3ui globalInvocationID_ {0u, 0u, 0u}; - u32 commandID_ {0u}; - u32 instanceID_ {0u}; - u32 vertexID_ {0u}; // might also be instance id + Vec3ui invocationID_ {0u, 0u, 0u}; + FragmentMode fragmentMode_ {}; bool livePatch_ {true}; ShaderDebugPatch patch_ {}; diff --git a/src/pipe.cpp b/src/pipe.cpp index 6ce3605f..c037848e 100644 --- a/src/pipe.cpp +++ b/src/pipe.cpp @@ -273,10 +273,10 @@ VKAPI_ATTR VkResult VKAPI_CALL CreateGraphicsPipelines( // be null e.g. if rasterizerDiscardEnable is true. // We can't rely on comparison of pColorBlendState to null here, // might be set to invalid pointer. - auto needsColorBlend = + pipe.needsColorBlend = colorAttachmentCount != 0u && !pci.pRasterizationState->rasterizerDiscardEnable; - if(needsColorBlend) { + if(pipe.needsColorBlend) { dlg_assert(pci.pColorBlendState); pipe.blendAttachments = { pci.pColorBlendState->pAttachments, diff --git a/src/pipe.hpp b/src/pipe.hpp index 2f2b5e0d..001ef981 100644 --- a/src/pipe.hpp +++ b/src/pipe.hpp @@ -92,6 +92,7 @@ struct GraphicsPipeline : Pipeline { bool hasTessellation : 1; bool hasDepthStencil : 1; bool hasMeshShader : 1; + bool needsColorBlend : 1; IntrusivePtr xfbPatch; // valid when we injected xfb diff --git a/src/shader.cpp b/src/shader.cpp index ba3d9983..c4ff442a 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -750,7 +750,7 @@ spc::Compiler& specializeSpirv(ShaderModule& mod, return compiled; } -std::unique_ptr copySpecializeSpirv(ShaderModule& mod, +std::unique_ptr copySpecializeSpirv(const ShaderModule& mod, const ShaderSpecialization& specialization, const std::string& entryPoint, u32 spvExecutionModel) { dlg_assert(mod.compiled); diff --git a/src/shader.hpp b/src/shader.hpp index 94b4bcf8..6d7ece2f 100644 --- a/src/shader.hpp +++ b/src/shader.hpp @@ -123,7 +123,7 @@ struct ShaderModule : SharedDeviceHandle { spc::Compiler& specializeSpirv(ShaderModule& mod, const ShaderSpecialization& specialization, const std::string& entryPoint, u32 spvExecutionModel); -std::unique_ptr copySpecializeSpirv(ShaderModule& mod, +std::unique_ptr copySpecializeSpirv(const ShaderModule& mod, const ShaderSpecialization& specialization, const std::string& entryPoint, u32 spvExecutionModel); diff --git a/src/util/buffmt.cpp b/src/util/buffmt.cpp index 7a0f9292..95d9a0a2 100644 --- a/src/util/buffmt.cpp +++ b/src/util/buffmt.cpp @@ -61,15 +61,13 @@ Type* buildType(const spc::Compiler& compiler, u32 typeID, if(memberDeco->decoration_flags.get(spv::DecorationMatrixStride)) { dst.deco.matrixStride = memberDeco->matrix_stride; } - // if(memberDeco->decoration_flags.get(spv::DecorationOffset)) { - // dst.deco.offset = memberDeco->offset; - // } } // handle array if(!stype->array.empty()) { - dlg_assert(meta && meta->decoration.decoration_flags.get(spv::DecorationArrayStride)); - dst.deco.arrayStride = meta->decoration.array_stride; + if(meta && meta->decoration.decoration_flags.get(spv::DecorationArrayStride)) { + dst.deco.arrayStride = meta->decoration.array_stride; + } dlg_assert(stype->array.size() == stype->array_size_literal.size()); dst.array = alloc.alloc(stype->array.size()); @@ -82,11 +80,14 @@ Type* buildType(const spc::Compiler& compiler, u32 typeID, } } - // apparently this is needed? not entirely sure why + dst.deco.arrayTypeID = typeID; + dlg_assert(stype->parent_type); typeID = stype->parent_type; stype = &compiler.get_type(typeID); meta = compiler.get_ir().find_meta(typeID); + + dst.deco.typeID = typeID; } if(stype->basetype == spc::SPIRType::Struct) { @@ -95,14 +96,15 @@ Type* buildType(const spc::Compiler& compiler, u32 typeID, for(auto i = 0u; i < stype->member_types.size(); ++i) { auto memTypeID = stype->member_types[i]; - dlg_assert(meta && meta->members.size() > i); - auto deco = &meta->members[i]; - auto off = deco->offset; + const spc::Meta::Decoration* deco {}; + if(meta && meta->members.size() > i) { + deco = &meta->members[i]; + } // TODO PERF: remove allocation via dlg format here, // use linearAllocator instead if needed auto name = dlg::format("?{}", i); - if(!deco->alias.empty()) { + if(deco && !deco->alias.empty()) { // TODO PERF: we copy here with new, terrible name = deco->alias; } @@ -110,7 +112,7 @@ Type* buildType(const spc::Compiler& compiler, u32 typeID, auto& mdst = dst.members[i]; mdst.type = buildType(compiler, memTypeID, alloc, deco); mdst.name = copy(alloc, name); - mdst.offset = off; + mdst.offset = deco ? deco->offset : 0u; if(!mdst.type) { return nullptr; diff --git a/src/util/buffmt.hpp b/src/util/buffmt.hpp index 6b850744..8217a55c 100644 --- a/src/util/buffmt.hpp +++ b/src/util/buffmt.hpp @@ -31,6 +31,7 @@ struct Decoration { u32 arrayStride {}; u32 matrixStride {}; u32 typeID {}; + u32 arrayTypeID {}; Flags flags {}; }; diff --git a/src/util/patch.cpp b/src/util/patch.cpp index 986a4a7b..e57cb20c 100644 --- a/src/util/patch.cpp +++ b/src/util/patch.cpp @@ -1,6 +1,7 @@ #include "command/alloc.hpp" #include #include +#include #include #include #include @@ -143,6 +144,7 @@ struct FuncInstrBuilder; struct ShaderPatch { constexpr static auto bufLayout = vil::BufferLayout::std430; + const Device& dev; const spc::Compiler& compiler; std::vector copy {}; spc::ParsedIR::SectionOffsets offsets {}; @@ -161,6 +163,7 @@ struct ShaderPatch { u32 const1 = u32(-1); LinAllocator alloc {}; + std::vector addInterface {}; template DeclInstrBuilder decl() { @@ -395,7 +398,7 @@ struct VariableCapture { u32 typeID; Type* parsed; - bool isFuncParameter {}; + bool isPointer {}; u32 offset {}; }; @@ -437,13 +440,24 @@ void fixDecorateCaptureType(ShaderPatch& patch, Type& type) { } if(!type.array.empty()) { - auto* meta = ir.find_meta(type.deco.typeID); + dlg_assert(type.deco.arrayTypeID != 0u); + auto* meta = ir.find_meta(type.deco.arrayTypeID); if(!meta || !meta->decoration.decoration_flags.get(spv::DecorationArrayStride)) { - type.deco.arrayStride = size(type, patch.bufLayout); + dlg_assert(type.deco.arrayStride == 0u); + + auto tarray = type.array; + type.array = {}; + type.deco.arrayStride = align( + size(type, patch.bufLayout), + align(type, patch.bufLayout)); + type.array = tarray; + patch.decl() - .push(type.deco.typeID) + .push(type.deco.arrayTypeID) .push(spv::DecorationArrayStride) .push(type.deco.arrayStride); + } else { + dlg_assert(type.deco.arrayStride); } } @@ -471,11 +485,14 @@ u32 findBuiltin(ShaderPatch& patch, return ret; } -u32 findOrDeclareBuiltinInput(ShaderPatch& patch, +std::pair findOrDeclareBuiltinInput(ShaderPatch& patch, const spc::SPIREntryPoint& entryPoint, spv::BuiltIn builtinType, u32 type) { u32 id = findBuiltin(patch, entryPoint, builtinType); if(id != u32(-1)) { - return id; + auto ptype = patch.compiler.get_ir().get(id).basetype; + auto& typeInfo = patch.compiler.get_ir().get(ptype); + dlg_assert(typeInfo.pointer); + return {id, u32(typeInfo.parent_type)}; } auto pointerType = ++patch.freeID; @@ -491,28 +508,59 @@ u32 findOrDeclareBuiltinInput(ShaderPatch& patch, .push(varID) .push(spv::StorageClassInput); - return varID; + patch.decl() + .push(varID) + .push(spv::DecorationBuiltIn) + .push(builtinType); + + patch.addInterface.push_back(varID); + + return {varID, type}; +} + +u32 loadBuiltinInput(ShaderPatch& patch, + const spc::SPIREntryPoint& entryPoint, spv::BuiltIn builtinType, u32 dstType) { + auto [varID, varType] = findOrDeclareBuiltinInput(patch, entryPoint, builtinType, dstType); + auto loaded = patch.genOp(spv::OpLoad, varType, varID); + + if(dstType == varType) { + return loaded; + } + + // NOTE: only valid for signed conversation when value is unsigned anyways + return patch.genOp(spv::OpBitcast, dstType, loaded); } u32 generateInvocationCompute(ShaderPatch& patch, const spc::SPIREntryPoint& entryPoint) { - u32 globalInvocationVar = findOrDeclareBuiltinInput(patch, entryPoint, + return loadBuiltinInput(patch, entryPoint, spv::BuiltInGlobalInvocationId, patch.typeUint3); - return patch.genOp(spv::OpLoad, patch.typeUint3, globalInvocationVar); } u32 generateInvocationVertex(ShaderPatch& patch, const spc::SPIREntryPoint& entryPoint) { - u32 drawIndexVar = findOrDeclareBuiltinInput(patch, entryPoint, - spv::BuiltInDrawIndex, patch.typeUint); - u32 instanceIndexVar = findOrDeclareBuiltinInput(patch, entryPoint, + auto drawIndex = patch.const0; + if(patch.dev.shaderDrawParameters) { + drawIndex = loadBuiltinInput(patch, entryPoint, + spv::BuiltInDrawIndex, patch.typeUint); + + auto& ir = patch.compiler.get_ir(); + + auto extName = "SPV_KHR_shader_draw_parameters"; + if(!contains(ir.declared_extensions, extName)) { + patch.decl().push(extName); + } + + auto capName = spv::CapabilityDrawParameters; + if(!contains(ir.declared_capabilities, capName)) { + patch.decl() .push(capName); + } + } + auto instanceIndex = loadBuiltinInput(patch, entryPoint, spv::BuiltInInstanceIndex, patch.typeUint); - u32 vertexIndexVar = findOrDeclareBuiltinInput(patch, entryPoint, + auto vertexIndex = loadBuiltinInput(patch, entryPoint, spv::BuiltInVertexIndex, patch.typeUint); - auto drawIndex = patch.genOp(spv::OpLoad, patch.typeUint, drawIndexVar); - auto instanceIndex = patch.genOp(spv::OpLoad, patch.typeUint, instanceIndexVar); - auto vertexIndex = patch.genOp(spv::OpLoad, patch.typeUint, vertexIndexVar); auto composited = patch.genOp(spv::OpCompositeConstruct, patch.typeUint3, drawIndex, instanceIndex, vertexIndex); @@ -521,11 +569,15 @@ u32 generateInvocationVertex(ShaderPatch& patch, u32 generateInvocationFragment(ShaderPatch& patch, const spc::SPIREntryPoint& entryPoint) { - u32 positionVar = findOrDeclareBuiltinInput(patch, entryPoint, + auto [posVar, posType] = findOrDeclareBuiltinInput(patch, entryPoint, spv::BuiltInFragCoord, patch.typeFloat4); + dlg_assert(posType == patch.typeFloat4); + (void) posType; - u32 fx = patch.genOp(spv::OpCompositeExtract, patch.typeFloat, positionVar, 0); - u32 fy = patch.genOp(spv::OpCompositeExtract, patch.typeFloat, positionVar, 0); + auto pos = patch.genOp(spv::OpLoad, patch.typeFloat4, posVar); + + u32 fx = patch.genOp(spv::OpCompositeExtract, patch.typeFloat, pos, 0); + u32 fy = patch.genOp(spv::OpCompositeExtract, patch.typeFloat, pos, 1); u32 ux = patch.genOp(spv::OpConvertFToU, patch.typeUint, fx); u32 uy = patch.genOp(spv::OpConvertFToU, patch.typeUint, fy); u32 composite = patch.genOp(spv::OpCompositeConstruct, patch.typeUint3, @@ -536,9 +588,8 @@ u32 generateInvocationFragment(ShaderPatch& patch, u32 generateInvocationRaytrace(ShaderPatch& patch, const spc::SPIREntryPoint& entryPoint) { - u32 launchIDVar = findOrDeclareBuiltinInput(patch, entryPoint, - spv::BuiltInLaunchIdKHR, patch.typeFloat4); - return patch.genOp(spv::OpLoad, patch.typeUint3, launchIDVar); + return loadBuiltinInput(patch, entryPoint, + spv::BuiltInLaunchIdKHR, patch.typeUint3); } u32 generateCurrentInvocation(ShaderPatch& patch, @@ -563,11 +614,12 @@ u32 generateCurrentInvocation(ShaderPatch& patch, } } -PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line, +PatchResult patchShaderCapture(const Device& dev, const spc::Compiler& compiler, + u32 file, u32 line, u64 captureAddress, const std::string& entryPointName, VkShaderStageFlagBits stage) { auto& ir = compiler.get_ir(); - ShaderPatch patch{compiler}; + ShaderPatch patch{dev, compiler}; // get entry point auto executionModel = executionModelFromStage(stage); @@ -618,12 +670,16 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line findDeclareBaseTypes(patch); declareConstants(patch); - // add extension - patch.decl().push("SPV_KHR_physical_storage_buffer"); + // allow us to use physical storage buffer pointers + auto extName = "SPV_KHR_physical_storage_buffer"; + if(!contains(ir.declared_extensions, extName)) { + patch.decl().push(extName); + } - // add capability - patch.decl() - .push(spv::CapabilityPhysicalStorageBufferAddresses); + auto capName = spv::CapabilityPhysicalStorageBufferAddresses; + if(!contains(ir.declared_capabilities, capName)) { + patch.decl() .push(capName); + } // parse local variables vil::Type baseType; @@ -635,7 +691,7 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line auto offset = 0u; std::vector captures; - auto addCapture = [&](u32 varID, u32 typeID, bool funcParam, const std::string& name) { + auto addCapture = [&](u32 varID, u32 typeID, bool isPointer, const std::string& name) { auto* parsedType = buildType(patch.compiler, typeID, patch.alloc); fixDecorateCaptureType(patch, *parsedType); @@ -646,7 +702,7 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line capture.typeID = typeID; capture.varID = varID; capture.offset = offset; - capture.isFuncParameter = funcParam; + capture.isPointer = isPointer; auto& member = members.emplace_back(); member.type = capture.parsed; @@ -682,7 +738,7 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line dlg_assert(srcType.pointer); dlg_assert(srcType.pointer_depth == 1u); - addCapture(varID, srcType.parent_type, false, name); + addCapture(varID, srcType.parent_type, true, name); } // capture function arguments @@ -697,7 +753,15 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line continue; } - addCapture(param.id, param.type, true, name); + auto typeID = param.type; + bool isPointer = false; + if(srcType.pointer) { + dlg_assert(srcType.pointer_depth == 1u); + typeID = srcType.parent_type; + isPointer = true; + } + + addCapture(param.id, typeID, isPointer, name); } // declare that struct type in spirv [patch] @@ -762,14 +826,14 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line for(auto [i, capture] : enumerate(captures)) { u32 memID {}; - if(capture.isFuncParameter) { - memID = capture.varID; - } else { + if(capture.isPointer) { memID = ++freeID; patch.instr(spv::OpLoad) .push(capture.typeID) .push(memID) .push(capture.varID); + } else { + memID = capture.varID; } builder.push(memID); @@ -895,6 +959,18 @@ PatchResult patchShaderCapture(const spc::Compiler& compiler, u32 file, u32 line // rest of the current block patch.instr(spv::OpLabel, blockRest); + // update interface of entry point + auto eOffset = patch.offsets.named.entry_points - ir.section_offsets.named.entry_points; + auto& instrHead = patch.copy[entryPoint.offset + eOffset]; + auto numWords = (instrHead >> 16u) & 0xFFFFu; + numWords += u32(patch.addInterface.size()); + dlg_assert(numWords < (1u << 16u)); + instrHead = (numWords << 16u) | (instrHead & 0xFFFFu); + + auto ioffset = entryPoint.interface_offset + eOffset; + patch.copy.insert(patch.copy.begin() + ioffset, + patch.addInterface.begin(), patch.addInterface.end()); + // update ID bound copy[3] = freeID + 1; @@ -930,11 +1006,85 @@ vku::Pipeline createPatchCopy(const ComputePipeline& src, VkShaderStageFlagBits vku::Pipeline createPatchCopy(const GraphicsPipeline& src, VkShaderStageFlagBits stage, span patchedSpv) { - (void) src; - (void) stage; - (void) patchedSpv; - dlg_error("unimplemented"); - return {}; + auto& dev = *src.dev; + + VkGraphicsPipelineCreateInfo gpi {}; + gpi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + gpi.layout = src.layout->handle; + gpi.pRasterizationState = &src.rasterizationState; + + if(!src.hasMeshShader) { + if(!src.dynamicState.count(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT)) { + gpi.pVertexInputState = &src.vertexInputState; + } + gpi.pInputAssemblyState = &src.inputAssemblyState; + } + + if(!src.rasterizationState.rasterizerDiscardEnable) { + gpi.pMultisampleState = &src.multisampleState; + gpi.pViewportState = &src.viewportState; + + if(src.hasDepthStencil) { + gpi.pDepthStencilState = &src.depthStencilState; + } + } + + if(src.hasTessellation) { + gpi.pTessellationState = &src.tessellationState; + } + + if(src.needsColorBlend) { + gpi.pColorBlendState = &src.colorBlendState; + } + + std::vector dynStates { + src.dynamicState.begin(), src.dynamicState.end()}; + VkPipelineDynamicStateCreateInfo dynState {}; + dynState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynState.dynamicStateCount = dynStates.size(); + dynState.pDynamicStates = dynStates.data(); + gpi.pDynamicState = &dynState; + + std::vector stages; + std::vector specInfos; + std::vector mods; + + specInfos.reserve(src.stages.size()); + + for(auto& srcStage : src.stages) { + // Create shader module + // We even do it for non-patched shaders, as the original modules + // might have been destroyed. + span spirv; + + if(srcStage.stage == stage) { + spirv = patchedSpv; + } else { + // normally, we use a mutex to access 'compiled'. But here + // we only access the immutable spirv so it is not needed. + spirv = srcStage.spirv->compiled->get_ir().spirv; + } + + auto& mod = mods.emplace_back(dev, spirv); + + // add stage + + auto& dstStage = stages.emplace_back(); + dstStage = {}; + dstStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + dstStage.stage = srcStage.stage; + dstStage.pName = srcStage.entryPoint.c_str(); + dstStage.module = mod.vkHandle(); + dstStage.pSpecializationInfo = + &specInfos.emplace_back(srcStage.specialization.vkInfo()); + } + + gpi.stageCount = u32(stages.size()); + gpi.pStages = stages.data(); + gpi.renderPass = src.renderPass->handle; // TODO: can this be destroyed? + gpi.subpass = src.subpass; + + return vku::Pipeline(dev, gpi); } vku::Pipeline createPatchCopy(const RayTracingPipeline& src, VkShaderStageFlagBits stage, @@ -961,8 +1111,8 @@ vku::Pipeline createPatchCopy(const Pipeline& src, VkShaderStageFlagBits stage, } PatchJobResult patchJob(PatchJobData& data) { - auto patchRes = patchShaderCapture(*data.compiler, data.file, data.line, - data.captureAddress, data.entryPoint, data.stage); + auto patchRes = patchShaderCapture(*data.pipe->dev, *data.compiler, + data.file, data.line, data.captureAddress, data.entryPoint, data.stage); if(patchRes.copy.empty()) { PatchJobResult res {}; res.error = "Shader patching failed"; @@ -989,31 +1139,14 @@ PatchJobResult patchJob(PatchJobData& data) { #endif // VIL_OUTPUT_PATCHED_SPIRV auto pipe = createPatchCopy(*data.pipe, data.stage, patchRes.copy); - auto newName = "patched:" + pipeName; - nameHandle(pipe, newName); - - CommandHookUpdate hookUpdate; - hookUpdate.invalidate = true; - // hookUpdate.newTarget = std::move(data.hookTarget); - auto& ops = hookUpdate.newOps.emplace(); - ops.useCapturePipe = pipe.vkHandle(); - ops.capturePipeInput = data.captureInput; - - auto& dev = *data.pipe->dev; - - { - std::lock_guard lock(dev.mutex); - if(data.state == PatchJobState::canceled) { - PatchJobResult res {}; - res.error = "Canceled"; - return res; - } - - data.state = PatchJobState::installing; + if(!pipe.vkHandle()) { + PatchJobResult res {}; + res.error = "Creating pipeline failed"; + return res; } - auto& hook = *dev.commandHook; - hook.updateHook(std::move(hookUpdate)); + auto newName = "patched:" + pipeName; + nameHandle(pipe, newName); PatchJobResult res {}; res.pipe = std::move(pipe); diff --git a/src/util/patch.hpp b/src/util/patch.hpp index 92bfeeb9..18dcb3db 100644 --- a/src/util/patch.hpp +++ b/src/util/patch.hpp @@ -37,8 +37,6 @@ struct PatchJobData { u32 line; PatchJobState state {PatchJobState::started}; u64 captureAddress; - Vec3u32 captureInput {}; - // CommandHookTarget hookTarget; }; struct PatchJobResult {