diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2021-01-26 13:32:03 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-26 13:32:03 -0800 |
| commit | a90c850735664e2928e4cc961442a126c6859b97 (patch) | |
| tree | 8b5e12113319ae87c57f5e616408eb955fe85a2c | |
| parent | 50676c741e10ffe6f710c5de86387eaacd274a9a (diff) | |
Integrate reflection more deeply into gfx layer (#1677)
26 files changed, 645 insertions, 346 deletions
diff --git a/examples/gpu-printing/main.cpp b/examples/gpu-printing/main.cpp index 57e096043..6a4aabf3a 100644 --- a/examples/gpu-printing/main.cpp +++ b/examples/gpu-printing/main.cpp @@ -100,7 +100,7 @@ ComPtr<gfx::IShaderProgram> loadComputeProgram(slang::IModule* slangModule, char { gfx::StageType::Compute, code, codeEnd }, }; - gfx::IShaderProgram::Desc programDesc; + gfx::IShaderProgram::Desc programDesc = {}; programDesc.pipelineType = gfx::PipelineType::Compute; programDesc.kernels = &kernelDescs[0]; programDesc.kernelCount = 2; diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index 959aa2881..cdd998e35 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -33,6 +33,7 @@ // design choices in their abstraction layer. // #include "gfx/render.h" +#include "gfx-util/shader-cursor.h" #include "tools/graphics-app-framework/window.h" #include "slang-com-ptr.h" #include "source/core/slang-basic.h" @@ -64,146 +65,185 @@ static const Vertex kVertexData[kVertexCount] = struct HelloWorld { -// We will start with a function that will invoke the Slang compiler -// to generate target-specific code from a shader file, and then -// use that to initialize an API shader program. +// We will start with the code related to loading and using the Slang compiler. // -// Note that `Renderer` and `ShaderProgram` here are types from -// the graphics API abstraction layer, and *not* part of the -// Slang API. This function is representative of code that a user -// might write to integrate Slang into their renderer/engine. +// Applications interact with the Slang compiler through a "session" object. +// There are actually two types of session: // -ComPtr<gfx::IShaderProgram> loadShaderProgram(gfx::IRenderer* renderer) -{ - // First, we need to create a "session" for interacting with the Slang - // compiler. This scopes all of our application's interactions - // with the Slang library. At the moment, creating a session causes - // Slang to load and validate its standard library, so this is a - // somewhat heavy-weight operation. When possible, an application - // should try to re-use the same session across multiple compiles. - // - SlangSession* slangSession = spCreateSession(NULL); - - // A compile request represents a single invocation of the compiler, - // to process some inputs and produce outputs (or errors). - // - SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession); - - // We would like to request a single target (output) format: DirectX shader bytecode (DXBC) - int targetIndex = spAddCodeGenTarget(slangRequest, SLANG_DXBC); - - // We will specify the desired "profile" for this one target in terms of the - // DirectX "shader model" that should be supported. - // - spSetTargetProfile(slangRequest, targetIndex, spFindProfile(slangSession, "sm_4_0")); - - // A compile request can include one or more "translation units," which more or - // less amount to individual source files (think `.c` files, not the `.h` files they - // might include). - // - // For this example, our code will all be in the Slang language. The user may - // also specify HLSL input here, but that currently doesn't affect the compiler's - // behavior much. - // - int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr); - - // We will load source code for our translation unit from the file `shaders.slang`. - // There are also variations of this API for adding source code from application-provided buffers. - // - spAddTranslationUnitSourceFile(slangRequest, translationUnitIndex, "shaders.slang"); - - // Next we will specify the entry points we'd like to compile. - // It is often convenient to put more than one entry point in the same file, - // and the Slang API makes it convenient to use a single run of the compiler - // to compile all entry points. - // - // For each entry point, we need to specify the name of a function, the - // translation unit in which that function can be found, and the stage - // that we need to compile for (e.g., vertex, fragment, geometry, ...). - // - char const* vertexEntryPointName = "vertexMain"; - char const* fragmentEntryPointName = "fragmentMain"; - int vertexIndex = spAddEntryPoint(slangRequest, translationUnitIndex, vertexEntryPointName, SLANG_STAGE_VERTEX); - int fragmentIndex = spAddEntryPoint(slangRequest, translationUnitIndex, fragmentEntryPointName, SLANG_STAGE_FRAGMENT); - - // Once all of the input options for the compiler have been specified, - // we can invoke `spCompile` to run the compiler and see if any errors - // were detected. - // - const SlangResult compileRes = spCompile(slangRequest); +// * The *global session* represents a loaded instance of the Slang library +// (e.g., `slang.dll` and is used to scope allocations/resources that are +// truly global across all compiles, such as the Slang "standard library") +// +// * A *session* is used to scope one or more compile actions such as +// loading modules, generating code, and performing reflection. +// +// For our simple application, we will allocate a single session that is used +// for all compilation. +// +ComPtr<slang::IGlobalSession> slangGlobalSession; +ComPtr<slang::ISession> slangSession; - // Even if there were no errors that forced compilation to fail, the - // compiler may have produced "diagnostic" output such as warnings. - // We will go ahead and print that output here. - // - if(auto diagnostics = spGetDiagnosticOutput(slangRequest)) +// Many Slang API functions return detailed diagnostic information +// (error messages, warnings, etc.) as a "blob" of data, or return +// a null blob pointer instead if there were no issues. +// +// For convenience, we define a subroutine that will dump the information +// in a diagnostic blob if one is produced, and skip it otherwise. +// +void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob) +{ + if( diagnosticsBlob != nullptr ) { - reportError("%s", diagnostics); + reportError("%s", (const char*) diagnosticsBlob->getBufferPointer()); } +} - // If compilation failed, there is no point in continuing any further. - if(SLANG_FAILED(compileRes)) +// The main task an application cares about is compiling shader code +// from souce (if needed) and loading it through the chosen graphics API. +// +// In addition, an application may want to receive reflection information +// about the program, which is what a `slang::ProgramLayout` provides. +// +gfx::Result loadShaderProgram( + gfx::IRenderer* renderer, + gfx::IShaderProgram** outProgram) +{ + // The first step in interacting with the Slang API is to create a "global session," + // which represents an instance of the Slang API loaded from the library. + // + if( !slangGlobalSession ) { - spDestroyCompileRequest(slangRequest); - spDestroySession(slangSession); - return nullptr; + SLANG_RETURN_ON_FAIL(slang_createGlobalSession(SLANG_API_VERSION, slangGlobalSession.writeRef())); } - // If compilation was successful, then we will extract the code for - // our two entry points as "blobs". - // - // If you are using a D3D API, then your application may want to - // take advantage of the fact taht these blobs are binary compatible - // with the `ID3DBlob`, `ID3D10Blob`, etc. interfaces. - // - - ISlangBlob* vertexShaderBlob = nullptr; - spGetEntryPointCodeBlob(slangRequest, vertexIndex, 0, &vertexShaderBlob); - - ISlangBlob* fragmentShaderBlob = nullptr; - spGetEntryPointCodeBlob(slangRequest, fragmentIndex, 0, &fragmentShaderBlob); - - // We extract the begin/end pointers to the output code buffers - // using operations on the `ISlangBlob` interface. - // - char const* vertexCode = (char const*) vertexShaderBlob->getBufferPointer(); - char const* vertexCodeEnd = vertexCode + vertexShaderBlob->getBufferSize(); - - char const* fragmentCode = (char const*) fragmentShaderBlob->getBufferPointer(); - char const* fragmentCodeEnd = fragmentCode + fragmentShaderBlob->getBufferSize(); - - // Once we have extracted the output blobs, it is safe to destroy - // the compile request and even the session. + // Next, we need to create a compilation session (`slang::ISession`) that will provide + // a scope to all the compilation and loading of code we do. // - spDestroyCompileRequest(slangRequest); - spDestroySession(slangSession); - - // Now we use the operations of the example graphics API abstraction - // layer to load shader code into the underlying API. + // In an application like this, which doesn't make use of preprocessor-based specialization, + // we can create a single session and use it for the duration of the application. + // One important service the session provides is re-use of modules that have already + // been compiled, so that if two Slang files `import` the same module, the compiler + // will only load and check that module once. // - // Reminder: this section does not involve the Slang API at all. - // - - gfx::IShaderProgram::KernelDesc kernelDescs[] = + if( !slangSession ) { - { gfx::StageType::Vertex, vertexCode, vertexCodeEnd }, - { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd }, - }; + // When creating a session we need to tell it what code generation targets we may + // want code generated for. It is valid to have zero or more targets, but many + // applications will only want one, corresponding to the graphics API they plan to use. + // This application is currently hard-coded to use D3D11, so we set up for compilation + // to DX bytecode. + // + // Note: the `TargetDesc` can also be used to set things like optimization settings + // for each target, but this application doesn't care to set any of that stuff. + // + slang::TargetDesc targetDesc = {}; + targetDesc.format = SLANG_DXBC; + targetDesc.profile = spFindProfile(slangGlobalSession, "sm_4_0"); + + // The session can be set up with a few other options, notably: + // + // * Any search paths that should be used when resolving `import` or `#include` directives. + // + // * Any preprocessor macros to pre-define when reading in files. + // + // This application doesn't plan to make heavy use of the preprocessor, and all its + // shader files are in the same directory, so we just use the default options (which + // will lead to the only search path being the current working directory). + // + slang::SessionDesc sessionDesc = {}; + sessionDesc.targetCount = 1; + sessionDesc.targets = &targetDesc; + + SLANG_RETURN_ON_FAIL(slangGlobalSession->createSession(sessionDesc, slangSession.writeRef())); + } - gfx::IShaderProgram::Desc programDesc; + // Once the session has been created, we can start loading code into it. + // + // The simplest way to load code is by calling `loadModule` with the name of a Slang + // module. A call to `loadModule("MyStuff")` will behave more or less as if you + // wrote: + // + // import MyStuff; + // + // In a Slang shader file. The compiler will use its search paths to try to locate + // `MyModule.slang`, then compile and load that file. If a matching module had + // already been loaded previously, that would be used directly. + // + ComPtr<slang::IBlob> diagnosticsBlob; + slang::IModule* module = slangSession->loadModule("shaders", diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + if(!module) + return SLANG_FAIL; + + // Loading the `shaders` module will compile and check all the shader code in it, + // including the shader entry points we want to use. Now that the module is loaded + // we can look up those entry points by name. + // + // Note: If you are using this `loadModule` approach to load your shader code it is + // important to tag your entry point functions with the `[shader("...")]` attribute + // (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there + // is no umambiguous way for the compiler to know which functions represent entry + // points when it parses your code via `loadModule()`. + // + 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); + + // 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.pipelineType = gfx::PipelineType::Graphics; - programDesc.kernels = &kernelDescs[0]; - programDesc.kernelCount = 2; - - auto shaderProgram = renderer->createProgram(programDesc); - - // Once we've used the output blobs from the Slang compiler to initialize - // the API-specific shader program, we can release their memory. - // - vertexShaderBlob->release(); - fragmentShaderBlob->release(); + programDesc.slangProgram = linkedProgram; + SLANG_RETURN_ON_FAIL(renderer->createProgram(programDesc, outProgram)); - return shaderProgram; + return SLANG_OK; } // @@ -231,11 +271,10 @@ int gWindowHeight = 768; gfx::ApplicationContext* gAppContext; gfx::Window* gWindow; Slang::ComPtr<gfx::IRenderer> gRenderer; -ComPtr<gfx::IBufferResource> gConstantBuffer; -ComPtr<gfx::IPipelineLayout> gPipelineLayout; +//ComPtr<gfx::IShaderObjectLayout> gRootLayout; ComPtr<gfx::IPipelineState> gPipelineState; -ComPtr<gfx::IDescriptorSet> gDescriptorSet; +ComPtr<gfx::IShaderObject> gRootObject; ComPtr<gfx::IBufferResource> gVertexBuffer; @@ -269,26 +308,6 @@ Slang::Result initialize() if(SLANG_FAILED(res)) return res; } - // Create a constant buffer for passing the model-view-projection matrix. - // - // Note: the Slang API supports reflection which could be used - // to query the size of the `Uniform` constant buffer, but we - // will not deal with that here because Slang also supports - // applications that want to hard-code things like memory - // layout and parameter locations. - // - int constantBufferSize = 16 * sizeof(float); - - IBufferResource::Desc constantBufferDesc; - constantBufferDesc.init(constantBufferSize); - constantBufferDesc.setDefaults(IResource::Usage::ConstantBuffer); - constantBufferDesc.cpuAccessFlags = IResource::AccessFlag::Write; - - gConstantBuffer = gRenderer->createBufferResource( - IResource::Usage::ConstantBuffer, - constantBufferDesc); - if(!gConstantBuffer) return SLANG_FAIL; - // Now we will create objects needed to configur the "input assembler" // (IA) stage of the D3D pipeline. // @@ -318,57 +337,45 @@ Slang::Result initialize() // Now we will use our `loadShaderProgram` function to load // the code from `shaders.slang` into the graphics API. // - ComPtr<IShaderProgram> shaderProgram = loadShaderProgram(gRenderer); - if(!shaderProgram) return SLANG_FAIL; + ComPtr<IShaderProgram> shaderProgram; + SLANG_RETURN_ON_FAIL(loadShaderProgram(gRenderer, shaderProgram.writeRef())); - // Our example graphics API usess a "modern" D3D12/Vulkan style - // of resource binding, so now we will dive into describing and - // allocating "descriptor sets." + // In order to bind shader parameters to the pipeline, we need + // to know how those parameters were assigned to locations/bindings/registers + // for the target graphics API. // - // First, we need to construct a descriptor set *layout*. + // The Slang compiler assigns locations to parameters in a deterministic + // fashion, so it is possible for a programmer to hard-code locations + // into their application code that will match up with their shaders. // - IDescriptorSetLayout::SlotRangeDesc slotRanges[] = - { - IDescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::UniformBuffer), - }; - IDescriptorSetLayout::Desc descriptorSetLayoutDesc; - descriptorSetLayoutDesc.slotRangeCount = 1; - descriptorSetLayoutDesc.slotRanges = &slotRanges[0]; - auto descriptorSetLayout = gRenderer->createDescriptorSetLayout(descriptorSetLayoutDesc); - if(!descriptorSetLayout) return SLANG_FAIL; - - // Next we will allocate a pipeline layout, which specifies - // that we will render with only a single descriptor set bound. + // Hard-coding of locations can become intractable as an application needs + // to support more different target platforms and graphics APIs, as well + // as more shaders with different specialized variants. // - - IPipelineLayout::DescriptorSetDesc descriptorSets[] = - { - IPipelineLayout::DescriptorSetDesc( descriptorSetLayout ), - }; - IPipelineLayout::Desc pipelineLayoutDesc; - pipelineLayoutDesc.renderTargetCount = 1; - pipelineLayoutDesc.descriptorSetCount = 1; - pipelineLayoutDesc.descriptorSets = &descriptorSets[0]; - auto pipelineLayout = gRenderer->createPipelineLayout(pipelineLayoutDesc); - if(!pipelineLayout) return SLANG_FAIL; - - gPipelineLayout = pipelineLayout; - - // Once we have the descriptor set layout, we can allocate - // and fill in a descriptor set to hold our parameters. + // Rather than rely on hard-coded locations, our examples will make use of + // reflection information provided by the Slang compiler (see `programLayout` + // above), and our example graphics API layer will translate that reflection + // information into a layout for a "root shader object." // - auto descriptorSet = gRenderer->createDescriptorSet(descriptorSetLayout); - if(!descriptorSet) return SLANG_FAIL; - - descriptorSet->setConstantBuffer(0, 0, gConstantBuffer); - - gDescriptorSet = descriptorSet; + // The root object will store values/bindings for all of the parameters in + // the `shaderProgram`. At a conceptual level we can think of `rootObject` as + // representing the "global scope" of the shader program that was loaded; + // it has entries for each global shader parameter that was declared. + // + // Multiple root objects can be created from the same program, and will have + // separate storage for parameter values. + // + // Readers who are familiar with D3D12 or Vulkan might think of this root + // layout as being similar in spirit to a "root signature" or "pipeline layout." + // + ComPtr<IShaderObject> rootObject; + SLANG_RETURN_ON_FAIL(gRenderer->createRootShaderObject(shaderProgram, rootObject.writeRef())); + gRootObject = rootObject; // 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.pipelineLayout = gPipelineLayout; desc.inputLayout = inputLayout; desc.program = shaderProgram; desc.renderTargetCount = 1; @@ -398,34 +405,85 @@ void renderFrame() gRenderer->setClearColor(kClearColor); gRenderer->clearFrame(); - // We update our constant buffer per-frame, just for the purposes - // of the example, but we don't actually load different data - // per-frame (we always use an identity projection). + // We will update the model-view-projection matrix that is passed + // into the shader code via the `Uniforms` buffer on a per-frame + // basis, even though the data that is loaded does not change + // per-frame (we always use an identity matrix). // - if(float* data = (float*) gRenderer->map(gConstantBuffer, MapFlavor::WriteDiscard)) + static const float kIdentity[] = { - static const float kIdentity[] = - { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 }; - memcpy(data, kIdentity, sizeof(kIdentity)); - - gRenderer->unmap(gConstantBuffer); - } + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + // + // We know that `gRootObject` is a root shader object created + // from our program, and that it is set up to hold values for + // all the parameter of that program. In order to actually + // set values, we need to be able to look up the location + // of speciic parameter that we want to set. + // + // Our example graphics API layer supports this operation + // with the idea of a *shader cursor* which can be thought + // of as pointing "into" a particular shader object at + // some location/offset. This design choice abstracts over + // the many ways that different platforms and APIs represent + // the necessary offset information. + // + // We construct an initial shader cursor that points at the + // entire shader program. You can think of this as akin to + // a diretory path of `/` for the root directory in a file + // system. + // + ShaderCursor rootCursor(gRootObject); + // + // Next, we use a convenience overload of `operator[]` to + // navigate from the root cursor down to the parameter we + // want to set. + // + // The operation `rootCursor["Uniforms"]` looks up the + // offset/location of the global shader parameter `Uniforms` + // (which is a uniform/constant buffer), and the subsequent + // `["modelViewProjection"]` step navigates from there down + // to the member named `modelViewProjection` in that buffer. + // + // Once we have formed a cursor that "points" at the + // model-view projection matrix, we can set its data directly. + // + rootCursor["Uniforms"]["modelViewProjection"].setData(kIdentity, sizeof(kIdentity)); + // + // Some readers might be concerned about the performance o + // the above operations because of the use of strings. For + // those readers, here are two things to note: + // + // * While these `operator[]` steps do need to perform string + // comparisons, they do *not* make copies of the strings or + // perform any heap allocation. + // + // * There are other overloads of `operator[]` that use the + // *index* of a parameter/field instead of its name, and those + // operations have fixed/constant overhead and perform no + // string comparisons. The indices used are independent of + // the target platform and graphics API, and can thus be + // hard-coded even in cross-platform code. + // // Now we configure our graphics pipeline state by setting the - // PSO, binding our descriptor set (which references the - // constant buffer that we wrote to above), and setting - // some additional bits of state, before drawing our triangle. + // PSO, binding our root shader object to it (which references + // the `Uniforms` buffer that will filled in above). // gRenderer->setPipelineState(PipelineType::Graphics, gPipelineState); - gRenderer->setDescriptorSet(PipelineType::Graphics, gPipelineLayout, 0, gDescriptorSet); + gRenderer->bindRootShaderObject(PipelineType::Graphics, gRootObject); + // We also need to set up a few pieces of fixed-function pipeline + // state that are not bound by the pipeline state above. + // gRenderer->setVertexBuffer(0, gVertexBuffer, sizeof(Vertex)); gRenderer->setPrimitiveTopology(PrimitiveTopology::TriangleList); + // Finally, we are ready to issue a draw call for a single triangle. + // gRenderer->draw(3); // With that, we are done drawing for one frame, and ready for the next. diff --git a/examples/hello-world/shaders.slang b/examples/hello-world/shaders.slang index 2df26b3d9..fe35db9c8 100644 --- a/examples/hello-world/shaders.slang +++ b/examples/hello-world/shaders.slang @@ -38,6 +38,8 @@ struct VertexStageOutput CoarseVertex coarseVertex : CoarseVertex; float4 sv_position : SV_Position; }; + +[shader("vertex")] VertexStageOutput vertexMain( AssembledVertex assembledVertex) { @@ -54,6 +56,7 @@ VertexStageOutput vertexMain( // Fragment Shader +[shader("fragment")] float4 fragmentMain( CoarseVertex coarseVertex : CoarseVertex) : SV_Target { diff --git a/examples/heterogeneous-hello-world/main.cpp b/examples/heterogeneous-hello-world/main.cpp index 8cf3894ed..8610a5fa2 100644 --- a/examples/heterogeneous-hello-world/main.cpp +++ b/examples/heterogeneous-hello-world/main.cpp @@ -85,7 +85,7 @@ gfx::IShaderProgram* loadShaderProgram(gfx::IRenderer* renderer, unsigned char c { gfx::StageType::Compute, computeCode, computeCodeEnd }, }; - gfx::IShaderProgram::Desc programDesc; + gfx::IShaderProgram::Desc programDesc = {}; programDesc.pipelineType = gfx::PipelineType::Compute; programDesc.kernels = &kernelDescs[0]; programDesc.kernelCount = 1; diff --git a/examples/model-viewer/main.cpp b/examples/model-viewer/main.cpp index 720d421e2..384cc5eac 100644 --- a/examples/model-viewer/main.cpp +++ b/examples/model-viewer/main.cpp @@ -960,7 +960,7 @@ RefPtr<EffectVariant> createEffectVaraint( spDestroyCompileRequest(slangRequest); // We use the graphics API to load a program into the GPU - gfx::IShaderProgram::Desc programDesc; + gfx::IShaderProgram::Desc programDesc = {}; programDesc.pipelineType = gfx::PipelineType::Graphics; programDesc.kernels = kernelDescs.data(); programDesc.kernelCount = kernelDescs.size(); diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp index 23193496b..044c1805b 100644 --- a/examples/shader-toy/main.cpp +++ b/examples/shader-toy/main.cpp @@ -351,7 +351,7 @@ Result loadShaderProgram(gfx::IRenderer* renderer, ComPtr<gfx::IShaderProgram>& { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd }, }; - gfx::IShaderProgram::Desc programDesc; + gfx::IShaderProgram::Desc programDesc = {}; programDesc.pipelineType = gfx::PipelineType::Graphics; programDesc.kernels = &kernelDescs[0]; programDesc.kernelCount = 2; @@ -3692,6 +3692,18 @@ namespace slang virtual SLANG_NO_THROW SlangResult SLANG_MCALL addTargetCapability( SlangInt targetIndex, SlangCapabilityID capability) = 0; + + /** Get the (linked) program for a compile request, including all entry points. + + The resulting program will include all of the global-scope modules for the + translation units in the program, plus any modules that they `import` + (transitively), specialized to any global specialization arguments that + were provided via the API, as well as all entry points specified for compilation, + specialized to their entry-point specialization arguments. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getProgramWithEntryPoints( + slang::IComponentType** outProgram) = 0; + }; #define SLANG_UUID_ICompileRequest { 0x96d33993, 0x317c, 0x4db5, { 0xaf, 0xd8, 0x66, 0x6e, 0xe7, 0x72, 0x48, 0xe2 } }; @@ -4127,6 +4139,12 @@ SLANG_EXTERN_C SLANG_API SlangResult spCompileRequest_getProgram( SlangCompileRequest* request, slang::IComponentType** outProgram); +/** @see slang::ICompileRequest::getProgramWithEntryPoints +*/ +SLANG_EXTERN_C SLANG_API SlangResult spCompileRequest_getProgramWithEntryPoints( + SlangCompileRequest* request, + slang::IComponentType** outProgram); + /** @see slang::ICompileRequest::getEntryPoint */ SLANG_EXTERN_C SLANG_API SlangResult spCompileRequest_getEntryPoint( diff --git a/source/slang/slang-api.cpp b/source/slang/slang-api.cpp index 4595bac51..56dc44c04 100644 --- a/source/slang/slang-api.cpp +++ b/source/slang/slang-api.cpp @@ -620,6 +620,14 @@ SLANG_API SlangResult spCompileRequest_getProgram( return request->getProgram(outProgram); } +SLANG_API SlangResult spCompileRequest_getProgramWithEntryPoints( + slang::ICompileRequest* request, + slang::IComponentType** outProgram) +{ + SLANG_ASSERT(request); + return request->getProgramWithEntryPoints(outProgram); +} + SLANG_API SlangResult spCompileRequest_getModule( slang::ICompileRequest* request, SlangInt translationUnitIndex, diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 62def6f0f..ac2d72862 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1920,6 +1920,7 @@ namespace Slang virtual SLANG_NO_THROW SlangReflection* SLANG_MCALL getReflection() SLANG_OVERRIDE; virtual SLANG_NO_THROW void SLANG_MCALL setCommandLineCompilerMode() SLANG_OVERRIDE; virtual SLANG_NO_THROW SlangResult SLANG_MCALL addTargetCapability(SlangInt targetIndex, SlangCapabilityID capability) SLANG_OVERRIDE; + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getProgramWithEntryPoints(slang::IComponentType** outProgram) SLANG_OVERRIDE; EndToEndCompileRequest( diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index c82d5cf65..b711a5327 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -4324,6 +4324,13 @@ SlangResult EndToEndCompileRequest::getProgram(slang::IComponentType** outProgra return SLANG_OK; } +SlangResult EndToEndCompileRequest::getProgramWithEntryPoints(slang::IComponentType** outProgram) +{ + auto program = getSpecializedGlobalAndEntryPointsComponentType(); + *outProgram = Slang::ComPtr<slang::IComponentType>(program).detach(); + return SLANG_OK; +} + SlangResult EndToEndCompileRequest::getModule(SlangInt translationUnitIndex, slang::IModule** outModule) { auto module = getFrontEndReq()->getTranslationUnit(translationUnitIndex)->getModule(); diff --git a/tools/gfx-util/shader-cursor.cpp b/tools/gfx-util/shader-cursor.cpp index f80803bdd..769643e75 100644 --- a/tools/gfx-util/shader-cursor.cpp +++ b/tools/gfx-util/shader-cursor.cpp @@ -20,7 +20,7 @@ Result gfx::ShaderCursor::getDereferenced(ShaderCursor& outCursor) const } } -Result ShaderCursor::getField(const char* name, const char* nameEnd, ShaderCursor& outCursor) +Result ShaderCursor::getField(const char* name, const char* nameEnd, ShaderCursor& outCursor) const { // If this cursor is invalid, then can't possible fetch a field. // @@ -118,7 +118,7 @@ Result ShaderCursor::getField(const char* name, const char* nameEnd, ShaderCurso return SLANG_E_INVALID_ARG; } -ShaderCursor ShaderCursor::getElement(SlangInt index) +ShaderCursor ShaderCursor::getElement(SlangInt index) const { // TODO: need to auto-dereference various buffer types... diff --git a/tools/gfx-util/shader-cursor.h b/tools/gfx-util/shader-cursor.h index 3f5cfb090..04dddc3aa 100644 --- a/tools/gfx-util/shader-cursor.h +++ b/tools/gfx-util/shader-cursor.h @@ -42,7 +42,7 @@ struct ShaderCursor Result getDereferenced(ShaderCursor& outCursor) const; - ShaderCursor getDereferenced() + ShaderCursor getDereferenced() const { ShaderCursor result; getDereferenced(result); @@ -53,20 +53,20 @@ struct ShaderCursor /// points at. /// /// If the operation succeeds, then the field cursor is written to `outCursor`. - Result getField(const char* nameBegin, const char* nameEnd, ShaderCursor& outCursor); + Result getField(const char* nameBegin, const char* nameEnd, ShaderCursor& outCursor) const; - ShaderCursor getField(const char* name) + ShaderCursor getField(const char* name) const { ShaderCursor cursor; getField(name, nullptr, cursor); return cursor; } - ShaderCursor getElement(SlangInt index); + ShaderCursor getElement(SlangInt index) const; static Result followPath(const char* path, ShaderCursor& ioCursor); - ShaderCursor getPath(const char* path) + ShaderCursor getPath(const char* path) const { ShaderCursor result(*this); followPath(path, result); @@ -80,29 +80,45 @@ struct ShaderCursor , m_typeLayout(object->getElementTypeLayout()) {} - SlangResult setData(void const* data, size_t size) + SlangResult setData(void const* data, size_t size) const { return m_baseObject->setData(m_offset, data, size); } - SlangResult setObject(IShaderObject* object) + SlangResult setObject(IShaderObject* object) const { return m_baseObject->setObject(m_offset, object); } - SlangResult setResource(IResourceView* resourceView) + SlangResult setResource(IResourceView* resourceView) const { return m_baseObject->setResource(m_offset, resourceView); } - SlangResult setSampler(ISamplerState* sampler) + SlangResult setSampler(ISamplerState* sampler) const { return m_baseObject->setSampler(m_offset, sampler); } - SlangResult setCombinedTextureSampler(IResourceView* textureView, ISamplerState* sampler) + SlangResult setCombinedTextureSampler(IResourceView* textureView, ISamplerState* sampler) const { return m_baseObject->setCombinedTextureSampler(m_offset, textureView, sampler); } + + /// Produce a cursor to the field with the given `name`. + /// + /// This is a convenience wrapper around `getField()`. + ShaderCursor operator[](const char* name) const + { + return getField(name); + } + + /// Produce a cursor to the element or field with the given `index`. + /// + /// This is a convenience wrapper around `getElement()`. + ShaderCursor operator[](SlangInt index) const + { + return getElement(index); + } }; } diff --git a/tools/gfx/cuda/render-cuda.cpp b/tools/gfx/cuda/render-cuda.cpp index 0e4ee6c13..d1e320224 100644 --- a/tools/gfx/cuda/render-cuda.cpp +++ b/tools/gfx/cuda/render-cuda.cpp @@ -241,6 +241,8 @@ public: RefPtr<TextureCUDAResource> textureResource = nullptr; }; +class CUDAProgramLayout; + class CUDAShaderProgram : public IShaderProgram, public RefObject { public: @@ -255,6 +257,9 @@ public: CUmodule cudaModule = nullptr; CUfunction cudaKernel; String kernelName; + ComPtr<slang::IComponentType> slangProgram; + RefPtr<CUDAProgramLayout> layout; + ~CUDAShaderProgram() { if (cudaModule) @@ -1260,16 +1265,6 @@ private: return SLANG_OK; } - virtual SLANG_NO_THROW Result SLANG_MCALL createRootShaderObjectLayout( - slang::ProgramLayout* layout, IShaderObjectLayout** outLayout) override - { - RefPtr<CUDAProgramLayout> cudaLayout; - cudaLayout = new CUDAProgramLayout(layout); - cudaLayout->programLayout = layout; - *outLayout = cudaLayout.detach(); - return SLANG_OK; - } - virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject(IShaderObjectLayout* layout, IShaderObject** outObject) override { @@ -1280,10 +1275,13 @@ private: } virtual SLANG_NO_THROW Result SLANG_MCALL - createRootShaderObject(IShaderObjectLayout* layout, IShaderObject** outObject) override + createRootShaderObject(IShaderProgram* program, IShaderObject** outObject) override { + auto cudaProgram = dynamic_cast<CUDAShaderProgram*>(program); + auto cudaLayout = cudaProgram->layout; + RefPtr<CUDARootShaderObject> result = new CUDARootShaderObject(); - SLANG_RETURN_ON_FAIL(result->init(this, dynamic_cast<CUDAShaderObjectLayout*>(layout))); + SLANG_RETURN_ON_FAIL(result->init(this, cudaLayout)); *outObject = result.detach(); return SLANG_OK; } @@ -1300,6 +1298,11 @@ private: virtual SLANG_NO_THROW Result SLANG_MCALL createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) override { + if( desc.kernelCount == 0 ) + { + return createProgramFromSlang(this, desc, outProgram); + } + if (desc.kernelCount != 1) return SLANG_E_INVALID_ARG; RefPtr<CUDAShaderProgram> cudaProgram = new CUDAShaderProgram(); @@ -1307,6 +1310,22 @@ private: SLANG_CUDA_RETURN_ON_FAIL( cuModuleGetFunction(&cudaProgram->cudaKernel, cudaProgram->cudaModule, desc.kernels[0].entryPointName)); cudaProgram->kernelName = desc.kernels[0].entryPointName; + + auto slangProgram = desc.slangProgram; + if( slangProgram ) + { + cudaProgram->slangProgram = slangProgram; + + auto slangProgramLayout = slangProgram->getLayout(); + if(!slangProgramLayout) + return SLANG_FAIL; + + RefPtr<CUDAProgramLayout> cudaLayout; + cudaLayout = new CUDAProgramLayout(slangProgramLayout); + cudaLayout->programLayout = slangProgramLayout; + cudaProgram->layout = cudaLayout; + } + *outProgram = cudaProgram.detach(); return SLANG_OK; } diff --git a/tools/gfx/d3d11/render-d3d11.cpp b/tools/gfx/d3d11/render-d3d11.cpp index a2cf63a8f..40e617a1f 100644 --- a/tools/gfx/d3d11/render-d3d11.cpp +++ b/tools/gfx/d3d11/render-d3d11.cpp @@ -331,17 +331,9 @@ public: List<ComPtr<ID3D11SamplerState>> m_samplers; }; - class ShaderProgramImpl : public IShaderProgram, public RefObject + class ShaderProgramImpl : public GraphicsCommonShaderProgram { public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IShaderProgram* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderProgram) - return static_cast<IShaderProgram*>(this); - return nullptr; - } - public: ComPtr<ID3D11VertexShader> m_vertexShader; ComPtr<ID3D11PixelShader> m_pixelShader; ComPtr<ID3D11ComputeShader> m_computeShader; @@ -1785,6 +1777,11 @@ void D3D11Renderer::drawIndexed(UInt indexCount, UInt startIndex, UInt baseVerte Result D3D11Renderer::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) { + if( desc.kernelCount == 0 ) + { + return createProgramFromSlang(this, desc, outProgram); + } + if (desc.pipelineType == PipelineType::Compute) { auto computeKernel = desc.findKernel(StageType::Compute); @@ -1799,6 +1796,7 @@ Result D3D11Renderer::createProgram(const IShaderProgram::Desc& desc, IShaderPro RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(); shaderProgram->m_computeShader.swap(computeShader); + initProgramCommon(shaderProgram, desc); *outProgram = shaderProgram.detach(); return SLANG_OK; @@ -1822,6 +1820,7 @@ Result D3D11Renderer::createProgram(const IShaderProgram::Desc& desc, IShaderPro RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(); shaderProgram->m_vertexShader.swap(vertexShader); shaderProgram->m_pixelShader.swap(pixelShader); + initProgramCommon(shaderProgram, desc); *outProgram = shaderProgram.detach(); return SLANG_OK; diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp index 74dc19dd7..648fc3052 100644 --- a/tools/gfx/d3d12/render-d3d12.cpp +++ b/tools/gfx/d3d12/render-d3d12.cpp @@ -202,17 +202,9 @@ protected: UINT64 m_fenceValue; ///< The fence value when rendering this Frame is complete }; - class ShaderProgramImpl : public IShaderProgram, public RefObject + class ShaderProgramImpl : public GraphicsCommonShaderProgram { public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IShaderProgram* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderProgram) - return static_cast<IShaderProgram*>(this); - return nullptr; - } - public: PipelineType m_pipelineType; List<uint8_t> m_vertexShader; List<uint8_t> m_pixelShader; @@ -2955,6 +2947,11 @@ void D3D12Renderer::setDescriptorSet(PipelineType pipelineType, IPipelineLayout* Result D3D12Renderer::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) { + if( desc.kernelCount == 0 ) + { + return createProgramFromSlang(this, desc, outProgram); + } + RefPtr<ShaderProgramImpl> program(new ShaderProgramImpl()); program->m_pipelineType = desc.pipelineType; @@ -2971,6 +2968,7 @@ Result D3D12Renderer::createProgram(const IShaderProgram::Desc& desc, IShaderPro program->m_vertexShader.insertRange(0, (const uint8_t*) vertexKernel->codeBegin, vertexKernel->getCodeSize()); program->m_pixelShader.insertRange(0, (const uint8_t*) fragmentKernel->codeBegin, fragmentKernel->getCodeSize()); } + initProgramCommon(program, desc); *outProgram = program.detach(); return SLANG_OK; diff --git a/tools/gfx/open-gl/render-gl.cpp b/tools/gfx/open-gl/render-gl.cpp index 7ee73366b..49fd6b005 100644 --- a/tools/gfx/open-gl/render-gl.cpp +++ b/tools/gfx/open-gl/render-gl.cpp @@ -381,16 +381,8 @@ public: List<RefPtr<SamplerStateImpl>> m_samplers; }; - class ShaderProgramImpl : public IShaderProgram, public RefObject + class ShaderProgramImpl : public GraphicsCommonShaderProgram { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IShaderProgram* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderProgram) - return static_cast<IShaderProgram*>(this); - return nullptr; - } public: ShaderProgramImpl(WeakSink<GLRenderer>* renderer, GLuint id): m_renderer(renderer), @@ -1485,6 +1477,11 @@ SLANG_NO_THROW Result SLANG_MCALL Result GLRenderer::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) { + if( desc.kernelCount == 0 ) + { + return createProgramFromSlang(this, desc, outProgram); + } + auto programID = glCreateProgram(); if(desc.pipelineType == PipelineType::Compute ) { @@ -1534,7 +1531,9 @@ Result GLRenderer::createProgram(const IShaderProgram::Desc& desc, IShaderProgra return SLANG_FAIL; } - *outProgram = new ShaderProgramImpl(m_weakRenderer, programID); + RefPtr<ShaderProgramImpl> program = new ShaderProgramImpl(m_weakRenderer, programID); + initProgramCommon(program, desc); + *outProgram = program.detach(); return SLANG_OK; } diff --git a/tools/gfx/render-graphics-common.cpp b/tools/gfx/render-graphics-common.cpp index 747659d11..593d4708c 100644 --- a/tools/gfx/render-graphics-common.cpp +++ b/tools/gfx/render-graphics-common.cpp @@ -1287,27 +1287,36 @@ Result SLANG_MCALL } Result SLANG_MCALL GraphicsAPIRenderer::createRootShaderObject( - IShaderObjectLayout* rootLayout, IShaderObject** outObject) + IShaderProgram* program, + IShaderObject** outObject) { + auto commonProgram = dynamic_cast<GraphicsCommonShaderProgram*>(program); + RefPtr<ProgramVars> shaderObject; SLANG_RETURN_ON_FAIL(ProgramVars::create(this, - dynamic_cast<GraphicsCommonProgramLayout*>(rootLayout), + commonProgram->getLayout(), shaderObject.writeRef())); *outObject = shaderObject.detach(); return SLANG_OK; } -Result SLANG_MCALL GraphicsAPIRenderer::createRootShaderObjectLayout( - slang::ProgramLayout* layout, IShaderObjectLayout** outLayout) +Result GraphicsAPIRenderer::initProgramCommon( + GraphicsCommonShaderProgram* program, + IShaderProgram::Desc const& desc) { + auto slangProgram = desc.slangProgram; + if(!slangProgram) + return SLANG_OK; + + auto slangReflection = slangProgram->getLayout(0); + if(!slangReflection) + return SLANG_FAIL; + RefPtr<GraphicsCommonProgramLayout> programLayout; - auto slangReflection = layout; { GraphicsCommonProgramLayout::Builder builder(this); builder.addGlobalParams(slangReflection->getGlobalParamsVarLayout()); - // TODO: Also need to reflect entry points here. - SlangInt entryPointCount = slangReflection->getEntryPointCount(); for (SlangInt e = 0; e < entryPointCount; ++e) { @@ -1324,7 +1333,10 @@ Result SLANG_MCALL GraphicsAPIRenderer::createRootShaderObjectLayout( SLANG_RETURN_ON_FAIL(builder.build(programLayout.writeRef())); } - *outLayout = programLayout.detach(); + + program->m_slangProgram = slangProgram; + program->m_layout = programLayout; + return SLANG_OK; } @@ -1360,11 +1372,44 @@ SLANG_NO_THROW bool SLANG_MCALL gfx::GraphicsAPIRenderer::hasFeature(const char* return m_features.findFirstIndex([&](Slang::String x) { return x == featureName; }) != -1; } +GraphicsCommonShaderProgram::~GraphicsCommonShaderProgram() +{ + // Note: It might not seem like this destructor is needed at all, since + // it is empty. + // + // In pratice, though, it seems to be required because the `m_layout` + // field is declared in the coresponding header before the `GraphicsCommonProgramLayout` + // is declared (we only have a forward declaration). + // + // `m_layout` is a `RefPtr`, and it seems that the compiler (or at least + // the Visual Studio compiler) either cannot synthesize a destructor for + // the type that properly destructs the field, or it simply synthesizes + // an incorect destructor. + // + // I suspect that part of the problem stems from the way that `GraphicsCommonProgramLayout` + // inherits from `RefObject` via multiple inheritance. + // + // No matter what, defining the destructor here in a file where + // the declaration of `GraphicsCommonProgramLayout` is visible + // seems to result in a correct destructor being emitted. + // + // TODO: Ther simpler and more robust fix would be to move the declaration + // of `GraphicsCommonProgramLayout` and related types to the header. +} + +IShaderProgram* GraphicsCommonShaderProgram::getInterface(const Guid& guid) +{ + if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderProgram) + return static_cast<IShaderProgram*>(this); + return nullptr; +} + void GraphicsAPIRenderer::preparePipelineDesc(GraphicsPipelineStateDesc& desc) { - if (desc.rootShaderObjectLayout) + if (!desc.pipelineLayout) { - auto rootLayout = dynamic_cast<GraphicsCommonProgramLayout*>(desc.rootShaderObjectLayout); + auto program = dynamic_cast<GraphicsCommonShaderProgram*>(desc.program); + auto rootLayout = program->getLayout(); desc.renderTargetCount = rootLayout->getRenderTargetCount(); desc.pipelineLayout = rootLayout->getPipelineLayout(); } @@ -1372,9 +1417,10 @@ void GraphicsAPIRenderer::preparePipelineDesc(GraphicsPipelineStateDesc& desc) void GraphicsAPIRenderer::preparePipelineDesc(ComputePipelineStateDesc& desc) { - if (desc.rootShaderObjectLayout) + if (!desc.pipelineLayout) { - auto rootLayout = dynamic_cast<GraphicsCommonProgramLayout*>(desc.rootShaderObjectLayout); + auto program = dynamic_cast<GraphicsCommonShaderProgram*>(desc.program); + auto rootLayout = program->getLayout(); desc.pipelineLayout = rootLayout->getPipelineLayout(); } } diff --git a/tools/gfx/render-graphics-common.h b/tools/gfx/render-graphics-common.h index 1c04302ce..3a07f2993 100644 --- a/tools/gfx/render-graphics-common.h +++ b/tools/gfx/render-graphics-common.h @@ -5,6 +5,25 @@ namespace gfx { +class GraphicsCommonProgramLayout; + +class GraphicsCommonShaderProgram : public IShaderProgram, public Slang::RefObject +{ +public: + SLANG_REF_OBJECT_IUNKNOWN_ALL + + IShaderProgram* getInterface(const Slang::Guid& guid); + + GraphicsCommonProgramLayout* getLayout() const { return m_layout; } + +protected: + ~GraphicsCommonShaderProgram(); + +private: + friend class GraphicsAPIRenderer; + ComPtr<slang::IComponentType> m_slangProgram; + Slang::RefPtr<GraphicsCommonProgramLayout> m_layout; +}; class GraphicsAPIRenderer : public IRenderer, public Slang::RefObject { @@ -15,18 +34,21 @@ public: virtual SLANG_NO_THROW bool SLANG_MCALL hasFeature(const char* featureName) SLANG_OVERRIDE; virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObjectLayout( slang::TypeLayoutReflection* typeLayout, IShaderObjectLayout** outLayout) SLANG_OVERRIDE; - virtual SLANG_NO_THROW Result SLANG_MCALL createRootShaderObjectLayout( - slang::ProgramLayout* programLayout, IShaderObjectLayout** outLayout) SLANG_OVERRIDE; virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject(IShaderObjectLayout* layout, IShaderObject** outObject) SLANG_OVERRIDE; virtual SLANG_NO_THROW Result SLANG_MCALL createRootShaderObject( - IShaderObjectLayout* rootLayout, IShaderObject** outObject) SLANG_OVERRIDE; + IShaderProgram* program, + IShaderObject** outObject) SLANG_OVERRIDE; virtual SLANG_NO_THROW Result SLANG_MCALL bindRootShaderObject(PipelineType pipelineType, IShaderObject* object) SLANG_OVERRIDE; void preparePipelineDesc(GraphicsPipelineStateDesc& desc); void preparePipelineDesc(ComputePipelineStateDesc& desc); IRenderer* getInterface(const Slang::Guid& guid); + Result initProgramCommon( + GraphicsCommonShaderProgram* program, + IShaderProgram::Desc const& desc); + protected: Slang::List<Slang::String> m_features; }; diff --git a/tools/gfx/render.h b/tools/gfx/render.h index eeef00a8f..4c3b71645 100644 --- a/tools/gfx/render.h +++ b/tools/gfx/render.h @@ -71,6 +71,8 @@ enum class StageType ClosestHit, Miss, Callable, + Amplification, + Mesh, CountOf, }; @@ -124,9 +126,13 @@ public: struct Desc { PipelineType pipelineType; + KernelDesc const* kernels; Int kernelCount; + /// Use instead of `kernels`/`kernelCount` if loading a Slang program. + slang::IComponentType* slangProgram; + /// Find and return the kernel for `stage`, if present. KernelDesc const* findKernel(StageType stage) const { @@ -1026,14 +1032,15 @@ struct BlendDesc struct GraphicsPipelineStateDesc { IShaderProgram* program; - // Application should set either pipelineLayout or rootShaderObjectLayout, but not both. + + // If `pipelineLayout` is null, then layout information will be extracted + // from `program`, which must have been created with Slang reflection info. IPipelineLayout* pipelineLayout = nullptr; - // Application should set either pipelineLayout or rootShaderObjectLayout, but not both. - IShaderObjectLayout* rootShaderObjectLayout = nullptr; + IInputLayout* inputLayout; UInt framebufferWidth; UInt framebufferHeight; - UInt renderTargetCount = 0; // Not used if rootShaderObjectLayout is set. + UInt renderTargetCount = 0; // Only used if `pipelineLayout` is non-null DepthStencilDesc depthStencil; RasterizerDesc rasterizer; BlendDesc blend; @@ -1042,8 +1049,10 @@ struct GraphicsPipelineStateDesc struct ComputePipelineStateDesc { IShaderProgram* program; + + // If `pipelineLayout` is null, then layout information will be extracted + // from `program`, which must have been created with Slang reflection info. IPipelineLayout* pipelineLayout = nullptr; - IShaderObjectLayout* rootShaderObjectLayout = nullptr; }; class IPipelineState : public ISlangUnknown @@ -1197,16 +1206,6 @@ public: return layout; } - virtual SLANG_NO_THROW Result SLANG_MCALL createRootShaderObjectLayout( - slang::ProgramLayout* layout, IShaderObjectLayout** outLayout) = 0; - - inline ComPtr<IShaderObjectLayout> createRootShaderObjectLayout(slang::ProgramLayout* layout) - { - ComPtr<IShaderObjectLayout> result; - SLANG_RETURN_NULL_ON_FAIL(createRootShaderObjectLayout(layout, result.writeRef())); - return result; - } - virtual SLANG_NO_THROW Result SLANG_MCALL createShaderObject(IShaderObjectLayout* layout, IShaderObject** outObject) = 0; inline ComPtr<IShaderObject> createShaderObject(IShaderObjectLayout* layout) @@ -1216,12 +1215,12 @@ public: return object; } - virtual SLANG_NO_THROW Result SLANG_MCALL createRootShaderObject(IShaderObjectLayout* layout, IShaderObject** outObject) = 0; + virtual SLANG_NO_THROW Result SLANG_MCALL createRootShaderObject(IShaderProgram* program, IShaderObject** outObject) = 0; - inline ComPtr<IShaderObject> createRootShaderObject(IShaderObjectLayout* layout) + inline ComPtr<IShaderObject> createRootShaderObject(IShaderProgram* program) { ComPtr<IShaderObject> object; - SLANG_RETURN_NULL_ON_FAIL(createRootShaderObject(layout, object.writeRef())); + SLANG_RETURN_NULL_ON_FAIL(createRootShaderObject(program, object.writeRef())); return object; } diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp index 94154bc42..2072d52ef 100644 --- a/tools/gfx/renderer-shared.cpp +++ b/tools/gfx/renderer-shared.cpp @@ -1,6 +1,8 @@ #include "renderer-shared.h" #include "render-graphics-common.h" +using namespace Slang; + namespace gfx { @@ -27,5 +29,71 @@ IResource* TextureResource::getInterface(const Slang::Guid& guid) SLANG_NO_THROW IResource::Type SLANG_MCALL TextureResource::getType() { return m_type; } SLANG_NO_THROW ITextureResource::Desc* SLANG_MCALL TextureResource::getDesc() { return &m_desc; } +gfx::StageType mapStage(SlangStage stage) +{ + switch( stage ) + { + default: + return gfx::StageType::Unknown; + + case SLANG_STAGE_AMPLIFICATION: return gfx::StageType::Amplification; + case SLANG_STAGE_ANY_HIT: return gfx::StageType::AnyHit; + case SLANG_STAGE_CALLABLE: return gfx::StageType::Callable; + case SLANG_STAGE_CLOSEST_HIT: return gfx::StageType::ClosestHit; + case SLANG_STAGE_COMPUTE: return gfx::StageType::Compute; + case SLANG_STAGE_DOMAIN: return gfx::StageType::Domain; + case SLANG_STAGE_FRAGMENT: return gfx::StageType::Fragment; + case SLANG_STAGE_GEOMETRY: return gfx::StageType::Geometry; + case SLANG_STAGE_HULL: return gfx::StageType::Hull; + case SLANG_STAGE_INTERSECTION: return gfx::StageType::Intersection; + case SLANG_STAGE_MESH: return gfx::StageType::Mesh; + case SLANG_STAGE_MISS: return gfx::StageType::Miss; + case SLANG_STAGE_RAY_GENERATION: return gfx::StageType::RayGeneration; + case SLANG_STAGE_VERTEX: return gfx::StageType::Vertex; + } +} + +Result createProgramFromSlang(IRenderer* renderer, IShaderProgram::Desc const& originalDesc, IShaderProgram** outProgram) +{ + SlangInt targetIndex = 0; + auto slangProgram = originalDesc.slangProgram; + + auto programLayout = slangProgram->getLayout(targetIndex); + if(!programLayout) + return SLANG_FAIL; + + Int entryPointCount = (Int) programLayout->getEntryPointCount(); + if(entryPointCount == 0) + return SLANG_FAIL; + + List<IShaderProgram::KernelDesc> kernelDescs; + List<ComPtr<slang::IBlob>> kernelBlobs; + for( Int i = 0; i < entryPointCount; ++i ) + { + ComPtr<slang::IBlob> entryPointCodeBlob; + SLANG_RETURN_ON_FAIL(slangProgram->getEntryPointCode(i, targetIndex, entryPointCodeBlob.writeRef())); + + auto entryPointLayout = programLayout->getEntryPointByIndex(i); + + kernelBlobs.add(entryPointCodeBlob); + + IShaderProgram::KernelDesc kernelDesc; + kernelDesc.codeBegin = entryPointCodeBlob->getBufferPointer(); + kernelDesc.codeEnd = (const char*) kernelDesc.codeBegin + entryPointCodeBlob->getBufferSize(); + kernelDesc.entryPointName = entryPointLayout->getName(); + kernelDesc.stage = mapStage(entryPointLayout->getStage()); + + kernelDescs.add(kernelDesc); + } + SLANG_ASSERT(kernelDescs.getCount() == entryPointCount); + + IShaderProgram::Desc programDesc; + programDesc.pipelineType = originalDesc.pipelineType; + programDesc.slangProgram = slangProgram; + programDesc.kernelCount = kernelDescs.getCount(); + programDesc.kernels = kernelDescs.getBuffer(); + + return renderer->createProgram(programDesc, outProgram); +} } // namespace gfx diff --git a/tools/gfx/renderer-shared.h b/tools/gfx/renderer-shared.h index 47cc9328d..c2924a7fd 100644 --- a/tools/gfx/renderer-shared.h +++ b/tools/gfx/renderer-shared.h @@ -67,4 +67,7 @@ public: protected: Desc m_desc; }; + +Result createProgramFromSlang(IRenderer* renderer, IShaderProgram::Desc const& desc, IShaderProgram** outProgram); + } diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp index 88cd52ad7..5afe3e3b4 100644 --- a/tools/gfx/vulkan/render-vk.cpp +++ b/tools/gfx/vulkan/render-vk.cpp @@ -307,17 +307,9 @@ public: VkDeviceSize size; }; - class ShaderProgramImpl: public IShaderProgram, public RefObject + class ShaderProgramImpl: public GraphicsCommonShaderProgram { public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IShaderProgram* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IShaderProgram) - return static_cast<IShaderProgram*>(this); - return nullptr; - } - public: ShaderProgramImpl(const VulkanApi& api, PipelineType pipelineType): m_api(&api), @@ -2835,6 +2827,11 @@ void VKRenderer::setDescriptorSet(PipelineType pipelineType, IPipelineLayout* la Result VKRenderer::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) { + if( desc.kernelCount == 0 ) + { + return createProgramFromSlang(this, desc, outProgram); + } + RefPtr<ShaderProgramImpl> impl = new ShaderProgramImpl(m_api, desc.pipelineType); if( desc.pipelineType == PipelineType::Compute) { @@ -2849,6 +2846,7 @@ Result VKRenderer::createProgram(const IShaderProgram::Desc& desc, IShaderProgra impl->m_vertex = compileEntryPoint(*vertexKernel, VK_SHADER_STAGE_VERTEX_BIT, impl->m_buffers[0], impl->m_modules[0]); impl->m_fragment = compileEntryPoint(*fragmentKernel, VK_SHADER_STAGE_FRAGMENT_BIT, impl->m_buffers[1], impl->m_modules[1]); } + initProgramCommon(impl, desc); *outProgram = impl.detach(); return SLANG_OK; } diff --git a/tools/graphics-app-framework/gui.cpp b/tools/graphics-app-framework/gui.cpp index 777614d58..fded0d76a 100644 --- a/tools/graphics-app-framework/gui.cpp +++ b/tools/graphics-app-framework/gui.cpp @@ -139,7 +139,7 @@ GUI::GUI(Window* window, IRenderer* inRenderer) { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd }, }; - gfx::IShaderProgram::Desc programDesc; + gfx::IShaderProgram::Desc programDesc = {}; programDesc.pipelineType = gfx::PipelineType::Graphics; programDesc.kernels = &kernelDescs[0]; programDesc.kernelCount = 2; diff --git a/tools/render-test/render-test-main.cpp b/tools/render-test/render-test-main.cpp index 926887a77..6e35db639 100644 --- a/tools/render-test/render-test-main.cpp +++ b/tools/render-test/render-test-main.cpp @@ -111,6 +111,8 @@ protected: Options::ShaderProgramType shaderType, const ShaderCompilerUtil::Input& input); + virtual void finalizeImpl(); + uint64_t m_startTicks; // variables for state to be used for rendering... @@ -165,6 +167,8 @@ public: virtual Result writeBindingOutput(BindRoot* bindRoot, const char* fileName) override; protected: + virtual void finalizeImpl() SLANG_OVERRIDE; + ComPtr<IShaderObject> m_programVars; ShaderOutputPlan m_outputPlan; }; @@ -586,12 +590,11 @@ SlangResult ShaderObjectRenderTestApp::initialize( // Slang's reflection API to tell us what the parameters of the program are. // auto slangReflection = (slang::ProgramLayout*) spGetReflection(m_compilationOutput.output.getRequestForReflection()); - ComPtr<IShaderObjectLayout> programLayout = renderer->createRootShaderObjectLayout(slangReflection); // Once we have determined the layout of all the parameters we need to bind, // we will create a shader object to use for storing and binding those parameters. // - m_programVars = renderer->createRootShaderObject(programLayout); + m_programVars = renderer->createRootShaderObject(m_shaderProgram); // Now we need to assign from the input parameter data that was parsed into // the program vars we allocated. @@ -601,9 +604,6 @@ SlangResult ShaderObjectRenderTestApp::initialize( m_renderer = renderer; - // TODO(tfoley): use each API's reflection interface to query the constant-buffer size needed -// m_constantBufferSize = 16 * sizeof(float); - { switch(m_options.shaderType) { @@ -614,7 +614,6 @@ SlangResult ShaderObjectRenderTestApp::initialize( case Options::ShaderProgramType::Compute: { ComputePipelineStateDesc desc; - desc.rootShaderObjectLayout = programLayout.get(); desc.program = m_shaderProgram; m_pipelineState = renderer->createComputePipelineState(desc); @@ -648,7 +647,6 @@ SlangResult ShaderObjectRenderTestApp::initialize( SLANG_RETURN_ON_FAIL(renderer->createBufferResource(IResource::Usage::VertexBuffer, vertexBufferDesc, kVertexData, m_vertexBuffer.writeRef())); GraphicsPipelineStateDesc desc; - desc.rootShaderObjectLayout = programLayout.get(); desc.program = m_shaderProgram; desc.inputLayout = inputLayout; @@ -662,6 +660,12 @@ SlangResult ShaderObjectRenderTestApp::initialize( return m_pipelineState ? SLANG_OK : SLANG_FAIL; } +void ShaderObjectRenderTestApp::finalizeImpl() +{ + m_programVars = nullptr; + RenderTestApp::finalizeImpl(); +} + Result RenderTestApp::_initializeShaders( SlangSession* session, IRenderer* renderer, @@ -730,6 +734,18 @@ void RenderTestApp::runCompute() void RenderTestApp::finalize() { + finalizeImpl(); + + m_inputLayout = nullptr; + m_vertexBuffer = nullptr; + m_shaderProgram = nullptr; + m_pipelineState = nullptr; + + m_renderer = nullptr; +} + +void RenderTestApp::finalizeImpl() +{ } Result LegacyRenderTestApp::writeBindingOutput(BindRoot* bindRoot, const char* fileName) @@ -1328,7 +1344,9 @@ static SlangResult _innerMain(Slang::StdWriters* stdWriters, SlangSession* sessi app = new LegacyRenderTestApp(); SLANG_RETURN_ON_FAIL(app->initialize(session, renderer, options, input)); window->show(); - return window->runLoop(app); + SLANG_RETURN_ON_FAIL(window->runLoop(app)); + app->finalize(); + return SLANG_OK; } } diff --git a/tools/render-test/slang-support.cpp b/tools/render-test/slang-support.cpp index 300cab4d7..c6d2b971e 100644 --- a/tools/render-test/slang-support.cpp +++ b/tools/render-test/slang-support.cpp @@ -50,6 +50,44 @@ gfx::StageType translateStage(SlangStage slangStage) } } +void ShaderCompilerUtil::Output::set( + PipelineType pipelineType, + const IShaderProgram::KernelDesc* inKernelDescs, + Slang::Index kernelDescCount, + slang::IComponentType* inSlangProgram) +{ + kernelDescs.clear(); + kernelDescs.addRange(inKernelDescs, kernelDescCount); + slangProgram = inSlangProgram; + desc.pipelineType = pipelineType; + desc.kernels = kernelDescs.getBuffer(); + desc.kernelCount = kernelDescCount; + desc.slangProgram = inSlangProgram; +} + +void ShaderCompilerUtil::Output::reset() +{ + { + desc.pipelineType = PipelineType::Unknown; + desc.slangProgram = nullptr; + desc.kernels = nullptr; + desc.kernelCount = 0; + } + + kernelDescs.clear(); + if (m_requestForKernels && session) + { + spDestroyCompileRequest(m_requestForKernels); + } + if (m_extraRequestForReflection && session) + { + spDestroyCompileRequest(m_extraRequestForReflection); + } + session = nullptr; + m_requestForKernels = nullptr; + m_extraRequestForReflection = nullptr; +} + /* static */ SlangResult ShaderCompilerUtil::_compileProgramImpl(SlangSession* session, const Options& options, const Input& input, const ShaderCompileRequest& request, Output& out) { out.reset(); @@ -168,7 +206,8 @@ gfx::StageType translateStage(SlangStage slangStage) SLANG_RETURN_ON_FAIL(res); - + ComPtr<slang::IComponentType> linkedSlangProgram; + List<ShaderCompileRequest::EntryPoint> actualEntryPoints; if(input.passThrough == SLANG_PASS_THROUGH_NONE) { @@ -178,6 +217,7 @@ gfx::StageType translateStage(SlangStage slangStage) // loading of code. // auto reflection = slang::ProgramLayout::get(slangRequest); + SLANG_RETURN_ON_FAIL(spCompileRequest_getProgramWithEntryPoints(slangRequest, linkedSlangProgram.writeRef())); // Get the amount of entry points in reflection Index entryPointCount = Index(reflection->getEntryPointCount()); @@ -226,7 +266,7 @@ gfx::StageType translateStage(SlangStage slangStage) kernelDescs.add(kernelDesc); } - out.set(input.pipelineType, kernelDescs.getBuffer(), kernelDescs.getCount()); + out.set(input.pipelineType, kernelDescs.getBuffer(), kernelDescs.getCount(), linkedSlangProgram); return SLANG_OK; } @@ -277,6 +317,7 @@ gfx::StageType translateStage(SlangStage slangStage) SLANG_RETURN_ON_FAIL(_compileProgramImpl(session, options, input, request, out)); out.m_extraRequestForReflection = slangOutput.getRequestForReflection(); + out.desc.slangProgram = slangOutput.desc.slangProgram; slangOutput.m_requestForKernels = nullptr; return SLANG_OK; diff --git a/tools/render-test/slang-support.h b/tools/render-test/slang-support.h index 06651ec73..c08d18567 100644 --- a/tools/render-test/slang-support.h +++ b/tools/render-test/slang-support.h @@ -58,35 +58,12 @@ struct ShaderCompilerUtil struct Output { - void set(PipelineType pipelineType, const IShaderProgram::KernelDesc* inKernelDescs, Slang::Index kernelDescCount) - { - kernelDescs.clear(); - kernelDescs.addRange(inKernelDescs, kernelDescCount); - desc.pipelineType = pipelineType; - desc.kernels = kernelDescs.getBuffer(); - desc.kernelCount = kernelDescCount; - } - void reset() - { - { - desc.pipelineType = PipelineType::Unknown; - desc.kernels = nullptr; - desc.kernelCount = 0; - } - - kernelDescs.clear(); - if (m_requestForKernels && session) - { - spDestroyCompileRequest(m_requestForKernels); - } - if (m_extraRequestForReflection && session) - { - spDestroyCompileRequest(m_extraRequestForReflection); - } - session = nullptr; - m_requestForKernels = nullptr; - m_extraRequestForReflection = nullptr; - } + void set( + PipelineType pipelineType, + const IShaderProgram::KernelDesc* kernelDescs, + Slang::Index kernelDescCount, + slang::IComponentType* slangProgram); + void reset(); ~Output() { reset(); @@ -105,7 +82,8 @@ struct ShaderCompilerUtil } Slang::List<IShaderProgram::KernelDesc> kernelDescs; - IShaderProgram::Desc desc; + ComPtr<slang::IComponentType> slangProgram; + IShaderProgram::Desc desc = {}; /// Compile request that owns the lifetime of compiled kernel code. SlangCompileRequest* m_requestForKernels = nullptr; |
