diff options
| -rw-r--r-- | examples/nv-aftermath-example/README.md | 46 | ||||
| -rw-r--r-- | examples/nv-aftermath-example/main.cpp | 543 | ||||
| -rw-r--r-- | examples/nv-aftermath-example/shaders.slang | 81 | ||||
| -rw-r--r-- | premake5.lua | 82 | ||||
| -rw-r--r-- | slang.h | 12 | ||||
| -rw-r--r-- | source/compiler-core/slang-source-loc.cpp | 100 | ||||
| -rw-r--r-- | source/compiler-core/slang-source-loc.h | 20 | ||||
| -rwxr-xr-x | source/slang/slang-compiler.h | 30 | ||||
| -rw-r--r-- | source/slang/slang-emit-source-writer.cpp | 2 | ||||
| -rw-r--r-- | source/slang/slang-ir-obfuscate-loc.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 102 | ||||
| -rw-r--r-- | tools/gfx/d3d/d3d-swapchain.h | 10 | ||||
| -rw-r--r-- | tools/gfx/d3d/d3d-util.cpp | 60 | ||||
| -rw-r--r-- | tools/gfx/d3d/d3d-util.h | 4 | ||||
| -rw-r--r-- | tools/gfx/d3d11/d3d11-device.cpp | 38 | ||||
| -rw-r--r-- | tools/gfx/d3d12/d3d12-device.cpp | 50 | ||||
| -rw-r--r-- | tools/gfx/d3d12/d3d12-device.h | 2 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-device.cpp | 34 |
18 files changed, 1186 insertions, 34 deletions
diff --git a/examples/nv-aftermath-example/README.md b/examples/nv-aftermath-example/README.md new file mode 100644 index 000000000..133a4359f --- /dev/null +++ b/examples/nv-aftermath-example/README.md @@ -0,0 +1,46 @@ +Nsight Aftermath Crash Example +============================== + +* Demonstrates use of aftermath API to capture a dump with a GPU crash +* Uses the [obfuscation feature](https://github.com/shader-slang/slang/blob/master/docs/user-guide/a1-03-obfuscation.md) +* Uses an `emit` source map +* Demonstrates use of file system compile products +* Forces a crash via time out, executing a shader that is purposefully slow +* Can be used to capture D3D and Vulkan (change the device type in the sample) +* When enabled GFX is built to use Aftermath it's debug layer + * This disables D3D debug layer, as not possible to have both enabled +* NOTE! Will only capture Aftermath DebugInfo with a *debug* build + * Gfx only enables debugging info (and therefore aftermath) on *debug* builds + +This example is *not* enabled by default. Enabling requires requires... + +* Passing "--enable-aftermath=true" to the command line of `premake`. +* Having a copy of the [Nsight aftermath SDK](https://developer.nvidia.com/nsight-aftermath) in `external/nv-aftermath` directory. + +On windows the following would be reasonable.. + +``` +premake vs2019 --deps=true --enable-aftermath=true +``` + +Typically D3D12 debug run produces the following files... + +* fragment-0.dxil - Fragment DXIL +* fragment-0.map - The emit source map, maps locations in the the fragment kernel to the obfuscated source file +* vertex-0.dxil - Vertex DXIL +* vertex-0.map - The emit source map, maps locations in the vertex kernel to the obfuscated source file +* XXXX-obfuscated.map - The obfuscated source map. Will be referenced by the other source maps. Maps obfuscated locations to the original source. +* aftermath-dump-X.bin - The Aftermath crash capture/s +* aftermath-debug-info-X.bin - The Aftermath debug info/s + +Having emit source maps, can be useful as discussed in [the documentation](https://github.com/shader-slang/slang/blob/master/docs/user-guide/a1-03-obfuscation.md#emit-source-maps), but isn't a requirement. If emit source maps are disabled the source maps `fragment-0.map`/`vertex-0.map` will *not* be produced. In this scenario the mapping to the obfuscated source file is embedded into the kernel/s directly. + +A Vulkan run will emit "spv" files, D3D12 will emit "dxil" files and D3D11 will emit "dxbc" files. + +The example source describes how to switch between emit source files, and different devices. + +## Links + +* [nsight aftermath](https://developer.nvidia.com/nsight-aftermath) +* [obfuscation](https://github.com/shader-slang/slang/blob/master/docs/user-guide/a1-03-obfuscation.md) +* [source map](https://github.com/source-map/source-map-spec) diff --git a/examples/nv-aftermath-example/main.cpp b/examples/nv-aftermath-example/main.cpp new file mode 100644 index 000000000..24d59ae62 --- /dev/null +++ b/examples/nv-aftermath-example/main.cpp @@ -0,0 +1,543 @@ +// main.cpp + +#include <slang.h> +#include "slang-gfx.h" +#include "gfx-util/shader-cursor.h" +#include "tools/platform/window.h" +#include "slang-com-ptr.h" +#include "../../source/core/slang-io.h" +#include "source/core/slang-basic.h" +#include "examples/example-base/example-base.h" + +#include "GFSDK_Aftermath.h" +#include "GFSDK_Aftermath_GpuCrashDump.h" + +using namespace gfx; +using namespace Slang; + +// This example is based on the "triangle" sample. +// +// This examples purpose is to show how to use the aftermath SDK to capture +// a crash dump. +// +// * [nsight aftermath](https://developer.nvidia.com/nsight-aftermath) +// +// In addition it uses obfuscation and source maps to allow source level +// debugging via aftermath even with obfuscation. +// +// * [obfuscation](https://github.com/shader-slang/slang/blob/master/docs/user-guide/a1-03-obfuscation.md) +// * [source map](https://github.com/source-map/source-map-spec) + +struct Vertex +{ + float position[3]; + float color[3]; +}; + +static const int kVertexCount = 3; +static const Vertex kVertexData[kVertexCount] = +{ + { { 0, 0, 0.5 }, { 1, 0, 0 } }, + { { 0, 1, 0.5 }, { 0, 0, 1 } }, + { { 1, 0, 0.5 }, { 0, 1, 0 } }, +}; + +struct AftermathCrashExample : public WindowedAppBase +{ + void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob); + + gfx::Result loadShaderProgram( gfx::IDevice* device, gfx::IShaderProgram** outProgram); + + Slang::Result initialize(); + + virtual void renderFrame(int frameBufferIndex) override; + + void onAftermathCrash(const void* data, const uint32_t dataSizeInBytes); + + void onAftermathDebugInfo(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize); + + void onAftermathCrashDescription(PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription description); + + void onAftermathMarker(const void* pMarker, void** resolvedMarkerData, uint32_t* markerSize); + + // Create accessors so we don't have to use g prefixed variables. + gfx::IDevice* getDevice() { return gDevice; } + gfx::ICommandQueue* getQueue() { return gQueue; } + gfx::IFramebufferLayout* getFrameBufferLayout() { return gFramebufferLayout; } + gfx::ISwapchain* getSwapChain() { return gSwapchain; } + gfx::IRenderPassLayout* getRenderPassLayout() { return gRenderPass; } + Slang::List<Slang::ComPtr<gfx::IFramebuffer>>& getFrameBuffers() { return gFramebuffers; } + Slang::List<Slang::ComPtr<gfx::ITransientResourceHeap>>& getTransientHeaps() { return gTransientHeaps; } + + ComPtr<gfx::IPipelineState> m_pipelineState; + ComPtr<gfx::IBufferResource> m_vertexBuffer; + + /// A counter such that we can make aftermath dump file names unique + std::atomic<int> m_uniqueId = 0; +}; + +void AftermathCrashExample::diagnoseIfNeeded(slang::IBlob* diagnosticsBlob) +{ + if (diagnosticsBlob != nullptr) + { + printf("%s", (const char*)diagnosticsBlob->getBufferPointer()); + } +} + +void AftermathCrashExample::onAftermathCrash(const void* data, const uint32_t dataSizeInBytes) +{ + // NOTE! This method can be called from *any* thread. + const auto id = m_uniqueId++; + + // Dump out as a file + Slang::StringBuilder filename; + filename << "aftermath-dump-" << id << ".bin"; + + File::writeAllBytes(filename, data, dataSizeInBytes); + + //SLANG_BREAKPOINT(0); +} + +void AftermathCrashExample::onAftermathDebugInfo(const void* gpuCrashDump, const uint32_t gpuCrashDumpSize) +{ + const auto id = m_uniqueId++; + + // Dump out as a file + Slang::StringBuilder filename; + filename << "aftermath-debug-info-" << id << ".bin"; + + File::writeAllBytes(filename, gpuCrashDump, gpuCrashDumpSize); +} + +void AftermathCrashExample::onAftermathCrashDescription(PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription description) +{ + // Ignore for now +} + +void AftermathCrashExample::onAftermathMarker(const void* marker, void** resolvedMarkerData, uint32_t* markerSize) +{ + // Ignore for now +} + +struct FileSystemEntry +{ + SlangPathType type; ///< The type of the entry + String path; ///< The path to the entr +}; + +struct CompileProduct +{ + String fileName; ///< The filename to write the compile product out to + ComPtr<ISlangBlob> blob; ///< A blob holding the products contents +}; + +/* Currently the mechanism to access the contents of a compilation that might consist of many products is through +representing the contents as a "file system". + +The file system is just a somewhat convenient/simple in memory representation of the compilation products. + +This function transverses the file system and adds everything found into outEntries. +*/ +static SlangResult _findFileSystemContents(ISlangFileSystemExt* fileSystem, const char* rootPath, List<FileSystemEntry>& outEntries) +{ + { + SlangPathType type; + SLANG_RETURN_ON_FAIL(fileSystem->getPathType(rootPath, &type)); + outEntries.add(FileSystemEntry{ type, rootPath }); + } + + // A context used to hold state, when using enumeratePathContents + struct Context + { + List<FileSystemEntry>& entries; // The entries to be accumulated to + String path; // The path being enumerated + }; + + for (Index i = outEntries.getCount() - 1; i < outEntries.getCount(); ++i) + { + const auto& entry = outEntries[i]; + + // If it's a directory we want to traverse it's contents + if (entry.type == SLANG_PATH_TYPE_DIRECTORY) + { + Context context{outEntries, entry.path }; + + fileSystem->enumeratePathContents(entry.path.getBuffer(), + [](SlangPathType pathType, const char* name, void* userData) -> void { + Context* context = reinterpret_cast<Context*>(userData); + + const String path = Path::simplify(Path::combine(context->path, name)); + + context->entries.add({pathType, path}); + }, + &context); + } + } + + return SLANG_OK; +} + +/* This function takes a compile results file system, and finds items that should be written out. + +This is somewhat complicated because the names of products from different compilations might have the same names. +So a "prefix" is passed in, and for files that don't have unique names, they are uniqified via the prefix. + +The same product may appear in multiple compilations, for example obfuscated source maps so a product is not added +if there is already a product with the same name */ +static SlangResult _addCompileProducts(ISlangFileSystemExt* fileSystem, const char* prefix, List<CompileProduct>& ioProducts) +{ + List<FileSystemEntry> fileSystemEntries; + SLANG_RETURN_ON_FAIL(_findFileSystemContents(fileSystem, ".", fileSystemEntries)); + + for (const auto& fileSystemEntry : fileSystemEntries) + { + if (fileSystemEntry.type != SLANG_PATH_TYPE_FILE) + { + continue; + } + + const auto ext = Path::getPathExt(fileSystemEntry.path); + + String outFileName; + + // Some filenames need special handling, and their names are already unique + // Others will be the same between differen fileSystem that represent the + // compilation products. + // + // Source maps that are obfuscated are unique. + { + String inFileName = Path::getFileNameWithoutExt(fileSystemEntry.path); + + // If it's an obfuscated source map, it's name is already unique (it includes the hash) + const bool isUniqueName = (ext == toSlice("map") && inFileName.endsWith(toSlice("-obfuscated"))); + + StringBuilder buf; + // If it's not a uniquename make it unique via the prefix + if (!isUniqueName) + { + // Uniquify with the prefix + buf << prefix << "-"; + } + + buf << inFileName << "." << ext; + outFileName = buf; + } + + // If we have an output filename + if (outFileName.getLength()) + { + // And that filename isn't already used + if (ioProducts.findFirstIndex([&](const CompileProduct& product) -> bool { + return product.fileName == outFileName; + }) < 0) + { + ComPtr<ISlangBlob> blob; + SLANG_RETURN_ON_FAIL(fileSystem->loadFile(fileSystemEntry.path.getBuffer(), blob.writeRef())); + + // Add to the results + ioProducts.add(CompileProduct{ outFileName, blob }); + } + } + } + + return SLANG_OK; +} + +gfx::Result AftermathCrashExample::loadShaderProgram( + gfx::IDevice* device, + gfx::IShaderProgram** outProgram) +{ + ComPtr<slang::ISession> slangSession; + slangSession = device->getSlangSession(); + + // This is a little bit of a work around. + // + // We want to set some options that are only available + // via processCommandLineArguments, but we need a request to be able to set them up + // The setting actually sets the parameters on the Linkage, so they will be used for the later + // actual compilation + { + ComPtr<slang::ICompileRequest> request; + + SLANG_RETURN_ON_FAIL(slangSession->createCompileRequest(request.writeRef())); + + // Turn on obfuscation + // + // Turns on source map as the line directive, this will lead to an "emit source map" + // and no #line directives in generated source. + // + // It isn't necessary to use the "source-map" line directive mode, and just use + // #line directives, and have source locations to obfuscated source file directly embedded. + // + // To do this replace the line below with + // + // ``` + // const char* args[] = { "-obfuscate" }; + // ``` + const char* args[] = { "-obfuscate", "-line-directive-mode", "source-map" }; + + request->processCommandLineArguments(args, SLANG_COUNT_OF(args)); + + // Enable debug info + request->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_MAXIMAL); + } + + ComPtr<slang::IBlob> diagnosticsBlob; + slang::IModule* module = slangSession->loadModule("shaders", diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + if (!module) + return SLANG_FAIL; + + // Find the entry points + ComPtr<slang::IEntryPoint> vertexEntryPoint; + SLANG_RETURN_ON_FAIL(module->findEntryPointByName("vertexMain", vertexEntryPoint.writeRef())); + // + ComPtr<slang::IEntryPoint> fragmentEntryPoint; + SLANG_RETURN_ON_FAIL(module->findEntryPointByName("fragmentMain", fragmentEntryPoint.writeRef())); + + // At this point we have a few different Slang API objects that represent + // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. + // + // A single Slang module could contain many different entry points (e.g., + // four vertex entry points, three fragment entry points, and two compute + // shaders), and before we try to generate output code for our target API + // we need to identify which entry points we plan to use together. + // + // Modules and entry points are both examples of *component types* in the + // Slang API. The API also provides a way to build a *composite* out of + // other pieces, and that is what we are going to do with our module + // and entry points. + // + Slang::List<slang::IComponentType*> componentTypes; + componentTypes.add(module); + + // Later on when we go to extract compiled kernel code for our vertex + // and fragment shaders, we will need to make use of their order within + // the composition, so we will record the relative ordering of the entry + // points here as we add them. + int entryPointCount = 0; + int vertexEntryPointIndex = entryPointCount++; + componentTypes.add(vertexEntryPoint); + + int fragmentEntryPointIndex = entryPointCount++; + componentTypes.add(fragmentEntryPoint); + + // Actually creating the composite component type is a single operation + // on the Slang session, but the operation could potentially fail if + // something about the composite was invalid (e.g., you are trying to + // combine multiple copies of the same module), so we need to deal + // with the possibility of diagnostic output. + // + ComPtr<slang::IComponentType> linkedProgram; + SlangResult result = slangSession->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + linkedProgram.writeRef(), + diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + SLANG_RETURN_ON_FAIL(result); + + const Index targetIndex = 0; + + // Trigger compilation by requesting the code. + // Normally gfx would compile as needed. + { + ComPtr<ISlangBlob> code; + ComPtr<ISlangBlob> diagnostics; + + SLANG_RETURN_ON_FAIL(linkedProgram->getEntryPointCode(vertexEntryPointIndex, targetIndex, code.writeRef(), diagnostics.writeRef())); + SLANG_RETURN_ON_FAIL(linkedProgram->getEntryPointCode(fragmentEntryPointIndex, targetIndex, code.writeRef(), diagnostics.writeRef())); + } + + { + // We want to find all the compilation products. In particular we want to get the emit source map, and the obfuscated + // source maps + + List<CompileProduct> compileProducts; + + // The current mechanism for getting access to compilation products other than result blob/diagnostics is to + // return it as a compilation result "file system". + + ComPtr<ISlangMutableFileSystem> vertexFileSystem; + SLANG_RETURN_ON_FAIL(linkedProgram->getResultAsFileSystem(vertexEntryPointIndex, targetIndex, vertexFileSystem.writeRef())); + + ComPtr<ISlangMutableFileSystem> fragmentFileSystem; + SLANG_RETURN_ON_FAIL(linkedProgram->getResultAsFileSystem(fragmentEntryPointIndex, targetIndex, fragmentFileSystem.writeRef())); + + // Add the contents of the compile result file systems into compileProducts + // Some products might appear in both file systems, so compileProducts is just the unique products. + // Additionally because some products may have the same name, we pass in a "prefix" to make the products name + // unique. + SLANG_RETURN_ON_FAIL(_addCompileProducts(vertexFileSystem, "vertex", compileProducts)); + SLANG_RETURN_ON_FAIL(_addCompileProducts(fragmentFileSystem, "fragment", compileProducts)); + + // Now write all of the products out + for (const auto& product : compileProducts) + { + SLANG_RETURN_ON_FAIL(File::writeAllBytes(product.fileName, product.blob->getBufferPointer(), product.blob->getBufferSize())); + } + } + + // Once we've described the particular composition of entry points + // that we want to compile, we defer to the graphics API layer + // to extract compiled kernel code and load it into the API-specific + // program representation. + // + gfx::IShaderProgram::Desc programDesc = {}; + programDesc.slangGlobalScope = linkedProgram; + SLANG_RETURN_ON_FAIL(device->createProgram(programDesc, outProgram)); + + return SLANG_OK; +} + +static void GFSDK_AFTERMATH_CALL _crashCallback(const void* gpuCrashDump, const uint32_t gpuCrashDumpSize, void* userData) +{ + reinterpret_cast<AftermathCrashExample*>(userData)->onAftermathCrash(gpuCrashDump, gpuCrashDumpSize); +} + +static void GFSDK_AFTERMATH_CALL _debugInfoCallback(const void* gpuCrashDump, const uint32_t gpuCrashDumpSize, void* userData) +{ + reinterpret_cast<AftermathCrashExample*>(userData)->onAftermathDebugInfo(gpuCrashDump, gpuCrashDumpSize); +} + +static void GFSDK_AFTERMATH_CALL _crashDescriptionCallback(PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addDescription, void* userData) +{ + reinterpret_cast<AftermathCrashExample*>(userData)->onAftermathCrashDescription(addDescription); +} + +static void GFSDK_AFTERMATH_CALL _markerCallback(const void* marker, void* pUserData, void** resolvedMarkerData, uint32_t* markerSize) +{ + reinterpret_cast<AftermathCrashExample*>(pUserData)->onAftermathMarker(marker, resolvedMarkerData, markerSize); +} + +Slang::Result AftermathCrashExample::initialize() +{ + // Defer shader debug information callbacks until an actual GPU crash dump + // is generated. Increases memory footprint. + const uint32_t aftermathFeatureFlags = GFSDK_Aftermath_GpuCrashDumpFeatureFlags_DeferDebugInfoCallbacks; + + // As per docs must be called before any device is created + GFSDK_Aftermath_EnableGpuCrashDumps( + GFSDK_Aftermath_Version_API, + GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_DX | GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan, + aftermathFeatureFlags, + _crashCallback, + _debugInfoCallback, + _crashDescriptionCallback, + _markerCallback, + this); + + // Set to a specific render API as needed. Valid values are... + // + // * gfx::DeviceType::Default + // * gfx::DeviceType::Vulkan + // * gfx::DeviceType::DirectX12 + // * gfx::DeviceType::DirectX11 + + const gfx::DeviceType deviceType = gfx::DeviceType::Default; + + initializeBase("aftermath-crash-example", 1024, 768, deviceType); + + auto device = getDevice(); + + // We will create objects needed to configur the "input assembler" + // (IA) stage of the D3D pipeline. + // + // First, we create an input layout: + // + InputElementDesc inputElements[] = { + { "POSITION", 0, Format::R32G32B32_FLOAT, offsetof(Vertex, position) }, + { "COLOR", 0, Format::R32G32B32_FLOAT, offsetof(Vertex, color) }, + }; + auto inputLayout = gDevice->createInputLayout( + sizeof(Vertex), + &inputElements[0], + 2); + if (!inputLayout) return SLANG_FAIL; + + // Next we allocate a vertex buffer for our pre-initialized + // vertex data. + // + IBufferResource::Desc vertexBufferDesc; + vertexBufferDesc.type = IResource::Type::Buffer; + vertexBufferDesc.sizeInBytes = kVertexCount * sizeof(Vertex); + vertexBufferDesc.defaultState = ResourceState::VertexBuffer; + m_vertexBuffer = device->createBufferResource(vertexBufferDesc, &kVertexData[0]); + if (!m_vertexBuffer) return SLANG_FAIL; + + // Now we will use our `loadShaderProgram` function to load + // the code from `shaders.slang` into the graphics API. + // + ComPtr<IShaderProgram> shaderProgram; + SLANG_RETURN_ON_FAIL(loadShaderProgram(device, shaderProgram.writeRef())); + + // Following the D3D12/Vulkan style of API, we need a pipeline state object + // (PSO) to encapsulate the configuration of the overall graphics pipeline. + // + GraphicsPipelineStateDesc desc; + desc.inputLayout = inputLayout; + desc.program = shaderProgram; + desc.framebufferLayout = getFrameBufferLayout(); + auto pipelineState = device->createGraphicsPipelineState(desc); + if (!pipelineState) + return SLANG_FAIL; + + m_pipelineState = pipelineState; + + return SLANG_OK; +} + +void AftermathCrashExample::renderFrame(int frameBufferIndex) +{ + ComPtr<ICommandBuffer> commandBuffer = getTransientHeaps()[frameBufferIndex]->createCommandBuffer(); + auto renderEncoder = commandBuffer->encodeRenderCommands(gRenderPass, getFrameBuffers()[frameBufferIndex]); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = (float)windowWidth; + viewport.extentY = (float)windowHeight; + renderEncoder->setViewportAndScissor(viewport); + + auto rootObject = renderEncoder->bindPipeline(m_pipelineState); + + auto deviceInfo = getDevice()->getDeviceInfo(); + + ShaderCursor rootCursor(rootObject); + + rootCursor["Uniforms"]["modelViewProjection"].setData( + deviceInfo.identityProjectionMatrix, sizeof(float) * 16); + + // We are going to extra efforts to create a shader that we know will time + // out because we *want* a GPU "crash", such we can capture via nsight aftermath. + // The failCount is just a number that is large enought to make things take too long. + int32_t failCount = 0x3fffffff; + rootCursor["Uniforms"]["failCount"].setData(&failCount, sizeof(failCount)); + + // We also need to set up a few pieces of fixed-function pipeline + // state that are not bound by the pipeline state above. + // + renderEncoder->setVertexBuffer(0, m_vertexBuffer); + renderEncoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + // Finally, we are ready to issue a draw call for a single triangle. + // + renderEncoder->draw(3); + renderEncoder->endEncoding(); + commandBuffer->close(); + getQueue()->executeCommandBuffer(commandBuffer); + + // With that, we are done drawing for one frame, and ready for the next. + // + getSwapChain()->present(); + + // If the id changes means we have a capture and so can quit. + // On D3D11, the first present *doesn't* appear to crash. + if (m_uniqueId != 0) + { + platform::Application::quit(); + } +} + +// This macro instantiates an appropriate main function to +// run the application defined above. +PLATFORM_UI_MAIN(innerMain<AftermathCrashExample>) diff --git a/examples/nv-aftermath-example/shaders.slang b/examples/nv-aftermath-example/shaders.slang new file mode 100644 index 000000000..61588dd49 --- /dev/null +++ b/examples/nv-aftermath-example/shaders.slang @@ -0,0 +1,81 @@ +// This shader is purposefully designed to be so slow it will cause a GPU timeout/crash. + +// Uniform data to be passed from application -> shader. +cbuffer Uniforms +{ + float4x4 modelViewProjection; + + // We want to make things fail so we can get an aftermath capture, + // so lets have a count that makes things really slow. + int failCount; +} + +// Per-vertex attributes to be assembled from bound vertex buffers. +struct AssembledVertex +{ + float3 position : POSITION; + float3 color : COLOR; +}; + +// Output of the vertex shader, and input to the fragment shader. +struct CoarseVertex +{ + float3 color; +}; + +// Output of the fragment shader +struct Fragment +{ + float4 color; +}; + +// Vertex Shader + +struct VertexStageOutput +{ + CoarseVertex coarseVertex : CoarseVertex; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexStageOutput vertexMain( + AssembledVertex assembledVertex) +{ + VertexStageOutput output; + + float3 position = assembledVertex.position; + float3 color = assembledVertex.color; + + output.coarseVertex.color = color; + output.sv_position = mul(modelViewProjection, float4(position, 1.0)); + + return output; +} + +// Fragment Shader + +[shader("fragment")] +float4 fragmentMain( + CoarseVertex coarseVertex : CoarseVertex) : SV_Target +{ + float3 color = coarseVertex.color; + + float factor = 0.0f; + + // Waste lots of cycles + for (int i = 0; i < failCount; ++i) + { + factor += 1.0e-20 * sin(float(i & 0xffff)); + factor += 1.0e-21 * cos(float(i & 0xfff) + 1.0); + factor += 1.0e-8f * tan(float(i & 0xfffff)); + } + + factor = abs(factor); + + while (factor < 0.25) + { + factor += factor; + } + + return float4(color, 1.0) * factor; +} diff --git a/premake5.lua b/premake5.lua index edcd56b4e..c7ed254cb 100644 --- a/premake5.lua +++ b/premake5.lua @@ -220,6 +220,14 @@ newoption { allowed = { { "true", "True"}, { "false", "False" } } } +newoption { + trigger = "enable-aftermath", + description = "(Optional) Enable aftermath in GFX, and add aftermath crash example to project", + value = "bool", + default = "false", + allowed = { { "true", "True"}, { "false", "False" } } +} + buildLocation = _OPTIONS["build-location"] executeBinary = (_OPTIONS["execute-binary"] == "true") buildGlslang = (_OPTIONS["build-glslang"] == "true") @@ -236,6 +244,7 @@ deployGLSLang = (_OPTIONS["deploy-slang-glslang"] == "true") fullDebugValidation = (_OPTIONS["full-debug-validation"] == "true") enableAsan = (_OPTIONS["enable-asan"] == "true") dxOnVk = (_OPTIONS["dx-on-vk"] == "true") +enableAftermath = (_OPTIONS["enable-aftermath"] == "true") -- If stdlib embedding is enabled, disable stdlib source embedding by default disableStdlibSource = enableEmbedStdLib @@ -245,6 +254,17 @@ if enableEmbedStdLib and _OPTIONS["disable-stdlib-source"] ~= nil then disableStdlibSource = (_OPTIONS["disable-stdlib-source"] == "true") end +if enableAftermath then + aftermathPath = "external/nv-aftermath" + + if not os.isfile(path.join(aftermathPath, "nsight-aftermath-usage-guidelines.txt")) then + print("external/nv-aftermath directory must hold aftermath SDK") + os.exit(0) + end + + printf("Enabled aftermath") +end + -- Determine the target info targetInfo = slangUtil.getTargetInfo() @@ -809,6 +829,40 @@ example "cpu-com-example" example "cpu-hello-world" kind "ConsoleApp" +if enableAftermath then + example "nv-aftermath-example" + filter {} + + local aftermathIncludePath = path.join(aftermathPath, "include") + local aftermathLibPath = path.join(aftermathPath, "lib") + + -- Add the aftermath includes + + includedirs { aftermathIncludePath } + + -- Add the libs directory. + -- Additionally we need to copy dlls that are needed for aftermath usage such that they + -- are available from the executable. + + filter { "platforms:x86" } + local libPath = path.join(aftermathLibPath, "x86") + libdirs { libPath } + links { "GFSDK_Aftermath_Lib.x86" } + + postbuildcommands { + '{COPY} "$(SolutionDir)"' .. libPath .. '/*.* "%{cfg.targetdir}"' + } + + filter { "platforms:x64" } + local libPath = path.join(aftermathLibPath, "x64") + libdirs { libPath } + links { "GFSDK_Aftermath_Lib.x64" } + + postbuildcommands { + '{COPY} "$(SolutionDir)"' .. libPath .. '/*.* "%{cfg.targetdir}"' + } +end + -- Most of the other projects have more interesting configuration going -- on, so let's walk through them in order of increasing complexity. -- @@ -1024,6 +1078,34 @@ tool "gfx" '{COPY} "' .. path.getabsolute("tools/gfx/slang.slang") .. '" "%{cfg.targetdir}"', } end + + -- If aftermath is enabled we need a define to turn on debugging features withing GFX + + if enableAftermath then + defines { "GFX_NV_AFTERMATH" } + + local aftermathIncludePath = path.join(aftermathPath, "include") + local aftermathLibPath = path.join(aftermathPath, "lib") + + -- Add the aftermath includes + includedirs { aftermathIncludePath } + + -- Add the libs + -- + -- We don't copy the dlls as that is something the application should do. + + filter { "platforms:x86" } + local libPath = path.join(aftermathLibPath, "x86") + libdirs { libPath } + links { "GFSDK_Aftermath_Lib.x86" } + + filter { "platforms:x64" } + local libPath = path.join(aftermathLibPath, "x64") + libdirs { libPath } + links { "GFSDK_Aftermath_Lib.x64" } + + end + -- To special case that we may be building using cygwin on windows. If 'true windows' we build for dx12/vk and run the script -- If not we assume it's a cygwin/mingw type situation and remove files that aren't appropriate if targetInfo.isWindows then @@ -4513,6 +4513,18 @@ namespace slang IBlob** outCode, IBlob** outDiagnostics = nullptr) = 0; + /** Get the compilation result as a file system. + + Has the same requirements as getEntryPointCode. + + The result is not written to the actual OS file system, but is made avaiable as an + in memory representation. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( + SlangInt entryPointIndex, + SlangInt targetIndex, + ISlangMutableFileSystem** outFileSystem) = 0; + /** Compute a hash for the entry point at `entryPointIndex` for the chosen `targetIndex`. This computes a hash based on all the dependencies for this component type as well as the diff --git a/source/compiler-core/slang-source-loc.cpp b/source/compiler-core/slang-source-loc.cpp index 8710cf91f..f9014e0d8 100644 --- a/source/compiler-core/slang-source-loc.cpp +++ b/source/compiler-core/slang-source-loc.cpp @@ -190,12 +190,41 @@ void SourceView::addDefaultLineDirective(SourceLoc directiveLoc) m_entries.add(entry); } -SlangResult _findLocWithSourceMap(SourceManager* lookupSourceManager, SourceView* sourceView, SourceLoc loc, HandleSourceLoc& outLoc) +// Nominal-like types take into account line directives, and potentially source maps +static bool _isNominalLike(SourceLocType type) +{ + return type == SourceLocType::Nominal || type == SourceLocType::Emit; +} + +static bool _canFollowSourceMap(SourceFile* sourceFile, SourceLocType type) +{ + // If we don't have a source map we have nothing to follow + if (!sourceFile->getSourceMap()) + { + return false; + } + + // If it's obfuscated we can't follow if we are emitting + if (sourceFile->getSourceMapKind() == SourceMapKind::Obfuscated && + type == SourceLocType::Emit) + { + return false; + } + + return _isNominalLike(type); +} + +static SlangResult _findLocWithSourceMap(SourceManager* lookupSourceManager, SourceView* sourceView, SourceLoc loc, SourceLocType type, HandleSourceLoc& outLoc) { auto sourceFile = sourceView->getSourceFile(); + if (!_canFollowSourceMap(sourceFile, type)) + { + return SLANG_E_NOT_FOUND; + } + // Hold a list of sourceFiles visited so we can't end up in a loop of lookups - List<SourceFile*> sourceFiles; + ShortList<SourceFile*, 8> sourceFiles; sourceFiles.add(sourceFile); Index entryIndex = -1; @@ -240,8 +269,10 @@ SlangResult _findLocWithSourceMap(SourceManager* lookupSourceManager, SourceView sourceFiles.add(foundSourceFile); // If it has a source map, we try and look up the current location in it's source map - if (auto foundSourceMap = foundSourceFile->getSourceMap()) + if (_canFollowSourceMap(foundSourceFile, type)) { + auto foundSourceMap = foundSourceFile->getSourceMap(); + const auto foundEntryIndex = foundSourceMap->get().findEntry(entry.sourceLine, entry.sourceColumn); // If we found the entry repeat the lookup @@ -273,18 +304,30 @@ SlangResult _findLocWithSourceMap(SourceManager* lookupSourceManager, SourceView return SLANG_OK; } -HandleSourceLoc SourceView::getHandleLoc(SourceLoc loc, SourceLocType type) + +SlangResult SourceView::_findSourceMapLoc(SourceLoc loc, SourceLocType type, HandleSourceLoc& outLoc) { - // If it's nominal - if (type == SourceLocType::Nominal && m_sourceFile->getSourceMap()) + // We only do source map lookups with nominal + if (!_isNominalLike(type)) { - // TODO(JS): - // Ideally we'd do the lookup on the "current" source manager rather than the source manager on this - // view, which may be a parent to the current one. - auto lookupSourceManager = m_sourceFile->getSourceManager(); + return SLANG_E_NOT_FOUND; + } - HandleSourceLoc handleLoc; - if (SLANG_SUCCEEDED(_findLocWithSourceMap(lookupSourceManager, this, loc, handleLoc))) + // TODO(JS): + // Ideally we'd do the lookup on the "current" source manager rather than the source manager on this + // view, which may be a parent to the current one. + auto lookupSourceManager = m_sourceFile->getSourceManager(); + + HandleSourceLoc handleLoc; + SLANG_RETURN_ON_FAIL(_findLocWithSourceMap(lookupSourceManager, this, loc, type, outLoc)); + + return SLANG_OK; +} + +HandleSourceLoc SourceView::getHandleLoc(SourceLoc loc, SourceLocType type) +{ + { HandleSourceLoc handleLoc; + if (SLANG_SUCCEEDED(_findSourceMapLoc(loc, type, handleLoc))) { return handleLoc; } @@ -307,22 +350,21 @@ HandleSourceLoc SourceView::getHandleLoc(SourceLoc loc, SourceLocType type) HandleSourceLoc handleLoc; handleLoc.column = columnIndex + 1; handleLoc.line = lineIndex + 1; - - // Make up a default entry - StringSlicePool::Handle pathHandle = StringSlicePool::Handle(0); - - // Only bother looking up the entry information if we want a 'Normal' lookup - const int entryIndex = (type == SourceLocType::Nominal) ? findEntryIndex(loc) : -1; - if (entryIndex >= 0) + + // Only bother looking up the entry information if we want a 'Norminal'-like lookup + if (_isNominalLike(type)) { - const Entry& entry = m_entries[entryIndex]; - // Adjust the line - handleLoc.line += entry.m_lineAdjust; - // Get the pathHandle.. - pathHandle = entry.m_pathHandle; + const int entryIndex = findEntryIndex(loc); + if (entryIndex >= 0) + { + const Entry& entry = m_entries[entryIndex]; + // Adjust the line + handleLoc.line += entry.m_lineAdjust; + // Get the pathHandle.. + handleLoc.pathHandle = entry.m_pathHandle; + } } - handleLoc.pathHandle = pathHandle; return handleLoc; } @@ -371,6 +413,14 @@ PathInfo SourceView::getPathInfo(SourceLoc loc, SourceLocType type) return getViewPathInfo(); } + { + HandleSourceLoc handleLoc; + if (SLANG_SUCCEEDED(_findSourceMapLoc(loc, type, handleLoc))) + { + return _getPathInfoFromHandle(handleLoc.pathHandle); + } + } + const int entryIndex = findEntryIndex(loc); return _getPathInfoFromHandle((entryIndex >= 0) ? m_entries[entryIndex].m_pathHandle : StringSlicePool::Handle(0)); } diff --git a/source/compiler-core/slang-source-loc.h b/source/compiler-core/slang-source-loc.h index fafff7de6..5c78c4293 100644 --- a/source/compiler-core/slang-source-loc.h +++ b/source/compiler-core/slang-source-loc.h @@ -164,6 +164,16 @@ struct SourceRange SourceLoc end; }; +/// Source maps associated with files are could be of different uses. We use the SourceMapKind +/// to indicate the usage. +/// +/// If the source map is obfuscated reasonable/desirable to ignore them on emit (if we didn't we leak information, +/// and we don't emit into the locations in the obfuscated intermediate "file"). +enum class SourceMapKind +{ + Normal, ///< A regular source map + Obfuscated, ///< Obfuscated source map +}; // Pre-declare struct SourceManager; @@ -256,8 +266,11 @@ public: /// Get the source map associated with this file. If it's set when doing /// lookup for source locations, the source map will be used IBoxValue<SourceMap>* getSourceMap() const { return m_sourceMap; } + /// Get the source map kind + SourceMapKind getSourceMapKind() const { return m_sourceMapKind; } + /// Set a source map - void setSourceMap(IBoxValue<SourceMap>* sourceMap) { m_sourceMap = sourceMap; } + void setSourceMap(IBoxValue<SourceMap>* sourceMap, SourceMapKind sourceMapKind) { m_sourceMap = sourceMap; m_sourceMapKind = sourceMapKind; } /// Ctor SourceFile(SourceManager* sourceManager, const PathInfo& pathInfo, size_t contentSize); @@ -281,12 +294,15 @@ public: // If set then the locations in this file are really from locations from elsewhere, // where the SourceMap specifies that mapping ComPtr<IBoxValue<SourceMap>> m_sourceMap; + // What kind of source map it is (if there is one) + SourceMapKind m_sourceMapKind = SourceMapKind::Normal; }; enum class SourceLocType { Nominal, ///< The normal interpretation which takes into account #line directives and source maps Actual, ///< Ignores #line directives/source maps - and is the location as seen in the actual file + Emit, ///< Behaves the same as `Nominal` but ignores source maps. Used for Emit source locations. }; // A source location in a format a human might like to see @@ -395,6 +411,8 @@ class SourceView /// Get the pathInfo from a string handle. If it's 0, it will return the _getPathInfo PathInfo _getPathInfoFromHandle(StringSlicePool::Handle pathHandle) const; + SlangResult _findSourceMapLoc(SourceLoc loc, SourceLocType type, HandleSourceLoc& outLoc); + String m_viewPath; ///< Path to this view. If empty the path is the path to the SourceView SourceLoc m_initiatingSourceLoc; ///< An optional source loc that defines where this view was initiated from. SourceLoc(0) if not defined. diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index ca16bfb94..7d73599ba 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -297,6 +297,12 @@ namespace Slang SlangInt targetIndex, slang::IBlob** outCode, slang::IBlob** outDiagnostics) SLANG_OVERRIDE; + + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( + SlangInt entryPointIndex, + SlangInt targetIndex, + ISlangMutableFileSystem** outFileSystem) SLANG_OVERRIDE; + SLANG_NO_THROW SlangResult SLANG_MCALL specialize( slang::SpecializationArg const* specializationArgs, SlangInt specializationArgCount, @@ -851,6 +857,14 @@ namespace Slang return Super::getEntryPointCode(entryPointIndex, targetIndex, outCode, outDiagnostics); } + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( + SlangInt entryPointIndex, + SlangInt targetIndex, + ISlangMutableFileSystem** outFileSystem) SLANG_OVERRIDE + { + return Super::getResultAsFileSystem(entryPointIndex, targetIndex, outFileSystem); + } + SLANG_NO_THROW SlangResult SLANG_MCALL specialize( slang::SpecializationArg const* specializationArgs, SlangInt specializationArgCount, @@ -1067,6 +1081,14 @@ namespace Slang return Super::getEntryPointCode(entryPointIndex, targetIndex, outCode, outDiagnostics); } + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( + SlangInt entryPointIndex, + SlangInt targetIndex, + ISlangMutableFileSystem** outFileSystem) SLANG_OVERRIDE + { + return Super::getResultAsFileSystem(entryPointIndex, targetIndex, outFileSystem); + } + SLANG_NO_THROW SlangResult SLANG_MCALL specialize( slang::SpecializationArg const* specializationArgs, SlangInt specializationArgCount, @@ -1221,6 +1243,14 @@ namespace Slang return Super::getEntryPointCode(entryPointIndex, targetIndex, outCode, outDiagnostics); } + SLANG_NO_THROW SlangResult SLANG_MCALL getResultAsFileSystem( + SlangInt entryPointIndex, + SlangInt targetIndex, + ISlangMutableFileSystem** outFileSystem) SLANG_OVERRIDE + { + return Super::getResultAsFileSystem(entryPointIndex, targetIndex, outFileSystem); + } + SLANG_NO_THROW SlangResult SLANG_MCALL specialize( slang::SpecializationArg const* specializationArgs, SlangInt specializationArgCount, diff --git a/source/slang/slang-emit-source-writer.cpp b/source/slang/slang-emit-source-writer.cpp index 72696c94a..4ff547119 100644 --- a/source/slang/slang-emit-source-writer.cpp +++ b/source/slang/slang-emit-source-writer.cpp @@ -311,7 +311,7 @@ void SourceWriter::advanceToSourceLocation(const SourceLoc& sourceLocation) } // Workout the humane source location. - const HumaneSourceLoc humaneSourceLoc = getSourceManager()->getHumaneLoc(sourceLocation); + const HumaneSourceLoc humaneSourceLoc = getSourceManager()->getHumaneLoc(sourceLocation, SourceLocType::Emit); // If the location is valid, mark need to update, and the new location if (humaneSourceLoc.line > 0) diff --git a/source/slang/slang-ir-obfuscate-loc.cpp b/source/slang/slang-ir-obfuscate-loc.cpp index ecc16d2b2..860633106 100644 --- a/source/slang/slang-ir-obfuscate-loc.cpp +++ b/source/slang/slang-ir-obfuscate-loc.cpp @@ -362,8 +362,8 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) sourceMap->addEntry(entries[i]); } - // Associate the sourceMap with the obfuscated file - obfuscatedFile->setSourceMap(boxedSourceMap); + // Associate the sourceMap with the obfuscated file. + obfuscatedFile->setSourceMap(boxedSourceMap, SourceMapKind::Obfuscated); // Set the obfuscated map onto the module module->setObfuscatedSourceMap(boxedSourceMap); diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 760a119d1..e08bb2a62 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -3464,6 +3464,106 @@ SLANG_NO_THROW slang::ProgramLayout* SLANG_MCALL ComponentType::getLayout( return asExternal(programLayout); } +static ICastable* _findDiagnosticRepresentation(IArtifact* artifact) +{ + if (auto rep = findAssociatedRepresentation<IArtifactDiagnostics>(artifact)) + { + return rep; + } + + for (auto associated : artifact->getAssociated()) + { + if (isDerivedFrom(associated->getDesc().payload, ArtifactPayload::Diagnostics)) + { + return associated; + } + } + return nullptr; +} + +static IArtifact* _findObfuscatedSourceMap(IArtifact* artifact) +{ + // If we find any obfuscated source maps, we are done + for (auto associated : artifact->getAssociated()) + { + const auto desc = associated->getDesc(); + + if (isDerivedFrom(desc.payload, ArtifactPayload::SourceMap) && + isDerivedFrom(desc.style, ArtifactStyle::Obfuscated)) + { + return associated; + } + } + return nullptr; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getResultAsFileSystem( + SlangInt entryPointIndex, + Int targetIndex, + ISlangMutableFileSystem** outFileSystem) +{ + ComPtr<ISlangBlob> diagnostics; + ComPtr<ISlangBlob> code; + + SLANG_RETURN_ON_FAIL(getEntryPointCode(entryPointIndex, targetIndex, diagnostics.writeRef(), code.writeRef())); + + auto linkage = getLinkage(); + + auto target = linkage->targets[targetIndex]; + + auto targetProgram = getTargetProgram(target); + + IArtifact* artifact = targetProgram->getExistingEntryPointResult(entryPointIndex); + + // Add diagnostics id needs be... + if (diagnostics && !_findDiagnosticRepresentation(artifact)) + { + // Add as an associated + + auto diagnosticsArtifact = Artifact::create(ArtifactDesc::make(Artifact::Kind::HumanText, ArtifactPayload::Diagnostics)); + diagnosticsArtifact->addRepresentationUnknown(diagnostics); + + artifact->addAssociated(diagnosticsArtifact); + + SLANG_ASSERT(diagnosticsArtifact == _findDiagnosticRepresentation(artifact)); + } + + // Add obfuscated source maps + if (!_findObfuscatedSourceMap(artifact)) + { + List<IRModule*> irModules; + enumerateIRModules([&](IRModule* irModule) -> void { irModules.add(irModule); }); + + for (auto irModule : irModules) + { + if (auto obfuscatedSourceMap = irModule->getObfuscatedSourceMap()) + { + auto artifactDesc = ArtifactDesc::make(ArtifactKind::Json, ArtifactPayload::SourceMap, ArtifactStyle::Obfuscated); + + // Create the source map artifact + auto sourceMapArtifact = Artifact::create(artifactDesc, obfuscatedSourceMap->get().m_file.getUnownedSlice()); + + sourceMapArtifact->addRepresentation(obfuscatedSourceMap); + + // associate with the artifact + artifact->addAssociated(sourceMapArtifact); + } + } + } + + // Turn into a file system and return + ComPtr<ISlangMutableFileSystem> fileSystem(new MemoryFileSystem); + + // Filter the containerArtifact into things that can be written + ComPtr<IArtifact> writeArtifact; + SLANG_RETURN_ON_FAIL(ArtifactContainerUtil::filter(artifact, writeArtifact)); + SLANG_RETURN_ON_FAIL(ArtifactContainerUtil::writeContainer(writeArtifact, "", fileSystem)); + + *outFileSystem = fileSystem.detach(); + + return SLANG_OK; +} + SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getEntryPointCode( SlangInt entryPointIndex, Int targetIndex, @@ -4983,7 +5083,7 @@ SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifac if (name.getLength()) { auto sourceFile = sourceManager->findSourceFileByPathRecursively(name); - sourceFile->setSourceMap(sourceMap); + sourceFile->setSourceMap(sourceMap, SourceMapKind::Obfuscated); } } } diff --git a/tools/gfx/d3d/d3d-swapchain.h b/tools/gfx/d3d/d3d-swapchain.h index 0d4b3fafb..c9e0de82a 100644 --- a/tools/gfx/d3d/d3d-swapchain.h +++ b/tools/gfx/d3d/d3d-swapchain.h @@ -99,7 +99,15 @@ public: } virtual SLANG_NO_THROW Result SLANG_MCALL present() override { - if (SLANG_FAILED(m_swapChain->Present(m_desc.enableVSync ? 1 : 0, 0))) + const auto res = m_swapChain->Present(m_desc.enableVSync ? 1 : 0, 0); + + // We may want to wait for crash dump completion for some kinds of debugging scenarios + if (res == DXGI_ERROR_DEVICE_REMOVED || res == DXGI_ERROR_DEVICE_RESET) + { + D3DUtil::waitForCrashDumpCompletion(res); + } + + if (SLANG_FAILED(res)) { return SLANG_FAIL; } diff --git a/tools/gfx/d3d/d3d-util.cpp b/tools/gfx/d3d/d3d-util.cpp index e1ffc0efc..34d615744 100644 --- a/tools/gfx/d3d/d3d-util.cpp +++ b/tools/gfx/d3d/d3d-util.cpp @@ -14,6 +14,14 @@ #include "core/slang-basic.h" #include "core/slang-platform.h" +#ifdef GFX_NV_AFTERMATH +# include "GFSDK_Aftermath.h" +# include "GFSDK_Aftermath_Defines.h" +# include "GFSDK_Aftermath_GpuCrashDump.h" + +# include "core/slang-process.h" +#endif + namespace gfx { using namespace Slang; @@ -870,6 +878,58 @@ Result SLANG_MCALL reportD3DLiveObjects() return D3DUtil::reportLiveObjects(); } + +/* static */SlangResult D3DUtil::waitForCrashDumpCompletion(HRESULT res) +{ + // If it's not a device remove/reset then theres nothing to wait for + if (!(res == DXGI_ERROR_DEVICE_REMOVED || res == DXGI_ERROR_DEVICE_RESET)) + { + return SLANG_OK; + } + +#if GFX_NV_AFTERMATH + { + GFSDK_Aftermath_CrashDump_Status status = GFSDK_Aftermath_CrashDump_Status_Unknown; + if (GFSDK_Aftermath_GetCrashDumpStatus(&status) != GFSDK_Aftermath_Result_Success) + { + return SLANG_FAIL; + } + + const auto startTick = Process::getClockTick(); + const auto frequency = Process::getClockFrequency(); + + float timeOutInSecs = 1.0f; + + uint64_t timeOutTicks = uint64_t(frequency * timeOutInSecs) + 1; + + // Loop while Aftermath crash dump data collection has not finished or + // the application is still processing the crash dump data. + while (status != GFSDK_Aftermath_CrashDump_Status_CollectingDataFailed && + status != GFSDK_Aftermath_CrashDump_Status_Finished && + Process::getClockTick() - startTick < timeOutTicks) + { + // Sleep a couple of milliseconds and poll the status again. + Process::sleepCurrentThread(50); + if (GFSDK_Aftermath_GetCrashDumpStatus(&status) != GFSDK_Aftermath_Result_Success) + { + return SLANG_FAIL; + } + } + + if (status == GFSDK_Aftermath_CrashDump_Status_Finished) + { + return SLANG_OK; + } + else + { + return SLANG_E_TIME_OUT; + } + } +#endif + + return SLANG_OK; +} + /* static */SlangResult D3DUtil::findAdapters(DeviceCheckFlags flags, const AdapterLUID* adapterLUID, IDXGIFactory* dxgiFactory, List<ComPtr<IDXGIAdapter>>& outDxgiAdapters) { outDxgiAdapters.clear(); diff --git a/tools/gfx/d3d/d3d-util.h b/tools/gfx/d3d/d3d-util.h index a35928f47..ce40ec722 100644 --- a/tools/gfx/d3d/d3d-util.h +++ b/tools/gfx/d3d/d3d-util.h @@ -121,6 +121,10 @@ class D3DUtil static D3D12_RESOURCE_STATES getResourceState(ResourceState state); static SlangResult reportLiveObjects(); + + /// Call after a DXGI_ERROR_DEVICE_REMOVED/DXGI_ERROR_DEVICE_RESET on present, to wait for + /// dumping to complete. Will return SLANG_OK if wait happened successfully + static SlangResult waitForCrashDumpCompletion(HRESULT res); }; #if SLANG_GFX_HAS_DXR_SUPPORT diff --git a/tools/gfx/d3d11/d3d11-device.cpp b/tools/gfx/d3d11/d3d11-device.cpp index c10a608dc..96a5043fb 100644 --- a/tools/gfx/d3d11/d3d11-device.cpp +++ b/tools/gfx/d3d11/d3d11-device.cpp @@ -16,6 +16,12 @@ #include "d3d11-helper-functions.h" +#ifdef GFX_NV_AFTERMATH +# include "GFSDK_Aftermath.h" +# include "GFSDK_Aftermath_Defines.h" +# include "GFSDK_Aftermath_GpuCrashDump.h" +#endif + namespace gfx { @@ -23,6 +29,7 @@ using namespace Slang; namespace d3d11 { + SlangResult DeviceImpl::initialize(const Desc& desc) { SLANG_RETURN_ON_FAIL(slangContext.initialize( @@ -148,11 +155,42 @@ SlangResult DeviceImpl::initialize(const Desc& desc) m_device.writeRef(), &featureLevel, m_immediateContext.writeRef()); + +#ifdef GFX_NV_AFTERMATH + if (SLANG_SUCCEEDED(res)) + { + if (deviceCheckFlags & DeviceCheckFlag::UseDebug) + { + // Initialize Nsight Aftermath for this device. + // This combination of flags is not necessarily appropriate for real world usage + const uint32_t aftermathFlags = + GFSDK_Aftermath_FeatureFlags_EnableMarkers | // Enable event marker tracking. + GFSDK_Aftermath_FeatureFlags_CallStackCapturing | // Enable automatic call stack event markers. + GFSDK_Aftermath_FeatureFlags_EnableResourceTracking | // Enable tracking of resources. + GFSDK_Aftermath_FeatureFlags_GenerateShaderDebugInfo | // Generate debug information for shaders. + GFSDK_Aftermath_FeatureFlags_EnableShaderErrorReporting; // Enable additional runtime shader error reporting. + + auto initResult = GFSDK_Aftermath_DX11_Initialize( + GFSDK_Aftermath_Version_API, + aftermathFlags, + m_device); + + if (initResult != GFSDK_Aftermath_Result_Success) + { + SLANG_ASSERT_FAILURE("Unable to initialize aftermath"); + // Unable to initialize aftermath + return SLANG_FAIL; + } + } + } +#endif + // Check if successfully constructed - if so we are done. if (SLANG_SUCCEEDED(res)) { break; } + } // If res is failure, means all styles have have failed, and so initialization fails. if (SLANG_FAILED(res)) diff --git a/tools/gfx/d3d12/d3d12-device.cpp b/tools/gfx/d3d12/d3d12-device.cpp index 863326a94..68078b445 100644 --- a/tools/gfx/d3d12/d3d12-device.cpp +++ b/tools/gfx/d3d12/d3d12-device.cpp @@ -28,6 +28,12 @@ # include "../nvapi/nvapi-include.h" #endif +#ifdef GFX_NV_AFTERMATH +# include "GFSDK_Aftermath.h" +# include "GFSDK_Aftermath_Defines.h" +# include "GFSDK_Aftermath_GpuCrashDump.h" +#endif + namespace gfx { namespace d3d12 @@ -37,6 +43,13 @@ using namespace Slang; static const uint32_t D3D_FEATURE_LEVEL_12_2 = 0xc200; + +#if GFX_NV_AFTERMATH +/* static */const bool DeviceImpl::g_isAftermathEnabled = true; +#else +/* static */const bool DeviceImpl::g_isAftermathEnabled = false; +#endif + struct ShaderModelInfo { D3D_SHADER_MODEL shaderModel; @@ -286,7 +299,7 @@ Result DeviceImpl::_createDevice( D3D_FEATURE_LEVEL featureLevel, D3D12DeviceInfo& outDeviceInfo) { - if (m_dxDebug && (deviceCheckFlags & DeviceCheckFlag::UseDebug)) + if (m_dxDebug && (deviceCheckFlags & DeviceCheckFlag::UseDebug) && !g_isAftermathEnabled) { m_dxDebug->EnableDebugLayer(); } @@ -319,7 +332,7 @@ Result DeviceImpl::_createDevice( return SLANG_FAIL; } - if (m_dxDebug && (deviceCheckFlags & DeviceCheckFlag::UseDebug)) + if (m_dxDebug && (deviceCheckFlags & DeviceCheckFlag::UseDebug) && !g_isAftermathEnabled) { ComPtr<ID3D12InfoQueue> infoQueue; if (SLANG_SUCCEEDED(device->QueryInterface(infoQueue.writeRef()))) @@ -373,6 +386,35 @@ Result DeviceImpl::_createDevice( } } + +#ifdef GFX_NV_AFTERMATH + { + if ((deviceCheckFlags & DeviceCheckFlag::UseDebug) && g_isAftermathEnabled) + { + // Initialize Nsight Aftermath for this device. + // This combination of flags is not necessarily appropraite for real world usage + const uint32_t aftermathFlags = + GFSDK_Aftermath_FeatureFlags_EnableMarkers | // Enable event marker tracking. + GFSDK_Aftermath_FeatureFlags_CallStackCapturing | // Enable automatic call stack event markers. + GFSDK_Aftermath_FeatureFlags_EnableResourceTracking | // Enable tracking of resources. + GFSDK_Aftermath_FeatureFlags_GenerateShaderDebugInfo | // Generate debug information for shaders. + GFSDK_Aftermath_FeatureFlags_EnableShaderErrorReporting; // Enable additional runtime shader error reporting. + + auto initResult = GFSDK_Aftermath_DX12_Initialize( + GFSDK_Aftermath_Version_API, + aftermathFlags, + device); + + if ( initResult != GFSDK_Aftermath_Result_Success) + { + SLANG_ASSERT_FAILURE("Unable to initialize aftermath"); + // Unable to initialize + return SLANG_FAIL; + } + } + } +#endif + // Get the descs { adapter->GetDesc(&outDeviceInfo.m_desc); @@ -474,7 +516,9 @@ Result DeviceImpl::initialize(const Desc& desc) } #endif - if (ENABLE_DEBUG_LAYER || isGfxDebugLayerEnabled()) + + // If Aftermath is enabled, we can't enable the D3D12 debug layer as well + if (ENABLE_DEBUG_LAYER || isGfxDebugLayerEnabled() && !g_isAftermathEnabled) { m_D3D12GetDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)loadProc(d3dModule, "D3D12GetDebugInterface"); diff --git a/tools/gfx/d3d12/d3d12-device.h b/tools/gfx/d3d12/d3d12-device.h index 975ba419b..6bbdde9d0 100644 --- a/tools/gfx/d3d12/d3d12-device.h +++ b/tools/gfx/d3d12/d3d12-device.h @@ -56,6 +56,8 @@ public: ComPtr<ID3D12Debug> m_dxDebug; + static const bool g_isAftermathEnabled; + D3D12DeviceInfo m_deviceInfo; ID3D12Device* m_device = nullptr; ID3D12Device5* m_device5 = nullptr; diff --git a/tools/gfx/vulkan/vk-device.cpp b/tools/gfx/vulkan/vk-device.cpp index 654353d63..8f7a88886 100644 --- a/tools/gfx/vulkan/vk-device.cpp +++ b/tools/gfx/vulkan/vk-device.cpp @@ -18,6 +18,12 @@ #include "vk-helper-functions.h" +#ifdef GFX_NV_AFTERMATH +# include "GFSDK_Aftermath.h" +# include "GFSDK_Aftermath_Defines.h" +# include "GFSDK_Aftermath_GpuCrashDump.h" +#endif + namespace gfx { @@ -658,6 +664,34 @@ Result DeviceImpl::initVulkanInstanceAndDevice( m_queueFamilyIndex = m_api.findQueue(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); assert(m_queueFamilyIndex >= 0); +#if defined(GFX_NV_AFTERMATH) + VkDeviceDiagnosticsConfigCreateInfoNV aftermathInfo = {}; + + { + // Enable NV_device_diagnostic_checkpoints extension to be able to + // use Aftermath event markers. + deviceExtensions.add(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); + + // Enable NV_device_diagnostics_config extension to configure Aftermath + // features. + deviceExtensions.add(VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME); + + // Set up device creation info for Aftermath feature flag configuration. + VkDeviceDiagnosticsConfigFlagsNV aftermathFlags = + VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | // Enable automatic call stack checkpoints. + VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | // Enable tracking of resources. + VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV; // Generate debug information for shaders. + // Not available on the version of Vulkan currently building with. + //VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_ERROR_REPORTING_BIT_NV; // Enable additional runtime shader error reporting. + + aftermathInfo.sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV; + aftermathInfo.flags = aftermathFlags; + + aftermathInfo.pNext = deviceCreateInfo.pNext; + deviceCreateInfo.pNext = &aftermathInfo; + } +#endif + if (handles[2].handleValue == 0) { float queuePriority = 0.0f; |
