diff options
34 files changed, 1360 insertions, 2553 deletions
diff --git a/build/visual-studio/model-viewer/model-viewer.vcxproj b/build/visual-studio/model-viewer/model-viewer.vcxproj index 8c8044d48..0dc5b0119 100644 --- a/build/visual-studio/model-viewer/model-viewer.vcxproj +++ b/build/visual-studio/model-viewer/model-viewer.vcxproj @@ -168,21 +168,24 @@ <None Include="..\..\..\examples\model-viewer\shaders.slang" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\example-base\example-base.vcxproj"> + <Project>{37BED5B5-23FA-D81F-8C0C-F1167867813A}</Project> + </ProjectReference> <ProjectReference Include="..\slang\slang.vcxproj"> <Project>{DB00DA62-0533-4AFD-B59F-A67D5B3A0808}</Project> </ProjectReference> - <ProjectReference Include="..\core\core.vcxproj"> - <Project>{F9BE7957-8399-899E-0C49-E714FDDD4B65}</Project> - </ProjectReference> <ProjectReference Include="..\gfx\gfx.vcxproj"> <Project>{222F7498-B40C-4F3F-A704-DDEB91A4484A}</Project> </ProjectReference> <ProjectReference Include="..\gfx-util\gfx-util.vcxproj"> <Project>{F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B}</Project> </ProjectReference> - <ProjectReference Include="..\graphics-app-framework\graphics-app-framework.vcxproj"> + <ProjectReference Include="..\platform\platform.vcxproj"> <Project>{3565FE5E-4FA3-11EB-AE93-0242AC130002}</Project> </ProjectReference> + <ProjectReference Include="..\core\core.vcxproj"> + <Project>{F9BE7957-8399-899E-0C49-E714FDDD4B65}</Project> + </ProjectReference> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> diff --git a/build/visual-studio/platform/platform.vcxproj b/build/visual-studio/platform/platform.vcxproj index 63085d492..c6a693195 100644 --- a/build/visual-studio/platform/platform.vcxproj +++ b/build/visual-studio/platform/platform.vcxproj @@ -170,6 +170,7 @@ <ClInclude Include="..\..\..\tools\platform\gui.h" /> <ClInclude Include="..\..\..\tools\platform\model.h" /> <ClInclude Include="..\..\..\tools\platform\performance-counter.h" /> + <ClInclude Include="..\..\..\tools\platform\platform-api.h" /> <ClInclude Include="..\..\..\tools\platform\vector-math.h" /> <ClInclude Include="..\..\..\tools\platform\window.h" /> </ItemGroup> diff --git a/build/visual-studio/platform/platform.vcxproj.filters b/build/visual-studio/platform/platform.vcxproj.filters index f90263e01..0b802e734 100644 --- a/build/visual-studio/platform/platform.vcxproj.filters +++ b/build/visual-studio/platform/platform.vcxproj.filters @@ -18,6 +18,9 @@ <ClInclude Include="..\..\..\tools\platform\performance-counter.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\tools\platform\platform-api.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\..\..\tools\platform\vector-math.h"> <Filter>Header Files</Filter> </ClInclude> diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj index 714c318de..652ac4dc3 100644 --- a/build/visual-studio/slang/slang.vcxproj +++ b/build/visual-studio/slang/slang.vcxproj @@ -98,7 +98,7 @@ <PrecompiledHeader>NotUsing</PrecompiledHeader> <WarningLevel>Level4</WarningLevel> <TreatWarningAsError>true</TreatWarningAsError> - <PreprocessorDefinitions>_DEBUG;SLANG_DYNAMIC_EXPORT;SLANG_WITHOUT_EMBEDDED_STD_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_DEBUG;SLANG_DYNAMIC_EXPORT;SLANG_WITHOUT_EMBEDDED_STD_LIB;SLANG_ENABLE_IR_BREAK_ALLOC=1;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>..\..\..\external\spirv-headers\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <Optimization>Disabled</Optimization> @@ -119,7 +119,7 @@ <PrecompiledHeader>NotUsing</PrecompiledHeader> <WarningLevel>Level4</WarningLevel> <TreatWarningAsError>true</TreatWarningAsError> - <PreprocessorDefinitions>_DEBUG;SLANG_DYNAMIC_EXPORT;SLANG_WITHOUT_EMBEDDED_STD_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_DEBUG;SLANG_DYNAMIC_EXPORT;SLANG_WITHOUT_EMBEDDED_STD_LIB;SLANG_ENABLE_IR_BREAK_ALLOC=1;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>..\..\..\external\spirv-headers\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <Optimization>Disabled</Optimization> diff --git a/examples/example-base/example-base.cpp b/examples/example-base/example-base.cpp index b06bbdf7e..09891e555 100644 --- a/examples/example-base/example-base.cpp +++ b/examples/example-base/example-base.cpp @@ -1,4 +1,10 @@ #include "example-base.h" +#include <chrono> + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#endif using namespace Slang; using namespace gfx; @@ -20,6 +26,7 @@ Slang::Result WindowedAppBase::initializeBase(const char* title, int width, int // Initialize the rendering layer. IDevice::Desc deviceDesc = {}; + // deviceDesc.slang.targetFlags = SLANG_TARGET_FLAG_DUMP_IR; gfx::Result res = gfxCreateDevice(&deviceDesc, gDevice.writeRef()); if (SLANG_FAILED(res)) return res; @@ -158,3 +165,11 @@ void WindowedAppBase::windowSizeChanged() } } } + +int64_t getCurrentTime() { return std::chrono::high_resolution_clock::now().time_since_epoch().count(); } + +int64_t getTimerFrequency() { return std::chrono::high_resolution_clock::period::den; } + +#ifdef _WIN32 +void _Win32OutputDebugString(const char* str) { OutputDebugStringW(Slang::String(str).toWString().begin()); } +#endif diff --git a/examples/example-base/example-base.h b/examples/example-base/example-base.h index ab7522f06..67f722c11 100644 --- a/examples/example-base/example-base.h +++ b/examples/example-base/example-base.h @@ -4,6 +4,10 @@ #include "tools/platform/window.h" #include "source/core/slang-basic.h" +#ifdef _WIN32 +void _Win32OutputDebugString(const char* str); +#endif + struct WindowedAppBase { protected: @@ -35,6 +39,40 @@ public: virtual void finalize() { gQueue->wait(); } }; +int64_t getCurrentTime(); +int64_t getTimerFrequency(); + +template<typename ... TArgs> inline void reportError(const char* format, TArgs... args) +{ + printf(format, args...); +#ifdef _WIN32 + char buffer[4096]; + sprintf_s(buffer, format, args...); + _Win32OutputDebugString(buffer); +#endif +} + +template <typename... TArgs> inline void log(const char* format, TArgs... args) +{ + reportError(format, args...); +} + +// 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. +// +inline void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob) +{ + if (diagnosticsBlob != nullptr) + { + reportError("%s", (const char*)diagnosticsBlob->getBufferPointer()); + } +} + + template<typename TApp> int innerMain() { diff --git a/examples/experimental/model-viewer/main.cpp b/examples/experimental/model-viewer/main.cpp deleted file mode 100644 index d4bc21776..000000000 --- a/examples/experimental/model-viewer/main.cpp +++ /dev/null @@ -1,2446 +0,0 @@ -// This example is out of date and currently disabled from build. -// The `gfx` layer has been refactored with a new shader-object model -// that will greatly simplify shader binding and specialization. -// This example should be updated to use the shader-object API in `gfx`. - -#if 0 -// main.cpp - -// -// This example is much more involved than the `hello-world` example, -// so readers are encouraged to work through the simpler code first -// before diving into this application. We will gloss over parts of -// the code that are similar to the code in `hello-world`, and -// instead focus on the new code that is required to use Slang in -// more advanced ways. -// - -// We still need to include the Slang header to use the Slang API -// -#include <slang.h> -#include "slang-com-helper.h" -// We will again make use of a simple graphics API abstraction -// layer, just to keep the examples short and to the point. -// -#include "graphics-app-framework/model.h" -#include "slang-gfx.h" -#include "graphics-app-framework/vector-math.h" -#include "graphics-app-framework/window.h" -#include "graphics-app-framework/gui.h" -using namespace gfx; -using Slang::RefObject; -using Slang::RefPtr; -// We will use a few utilities from the C++ standard library, -// just to keep the code short. Note that the Slang API does -// not use or require any C++ standard library features. -// -#include <map> -#include <memory> -#include <string> -#include <sstream> -#include <vector> - -// A larger application will typically want to load/compile -// multiple modules/files of shader code. When using the -// Slang API, some one-time setup work can be amortized -// across multiple modules by using a single Slang -// "session" across multiple compiles. -// -// To that end, our application will use a function-`static` -// variable to create a session on demand and re-use it -// for the duration of the application. -// -SlangSession* getSlangSession() -{ - static SlangSession* slangSession = spCreateSession(NULL); - return slangSession; -} - -// This application is going to build its own layered -// application-specific abstractions on top of Slang, -// so it will have its own notion of a shader "module," -// which comprises the results of a Slang compilation, -// including the reflection information. -// -struct ShaderModule : RefObject -{ - // The file that the module was loaded from. - std::string inputPath; - - // Slang compile request and reflection data. - SlangCompileRequest* slangRequest; - slang::ShaderReflection* slangReflection; - - // Reference to the renderer, used to service requests - // that load graphics API objects based on the module. - Slang::ComPtr<gfx::IRenderer> renderer; -}; -// -// In order to load a shader module from a `.slang` file on -// disk, we will use a Slang compile session, much like -// how the earlier Hello World example loaded shader code. -// -// We will point out major differences between the earlier -// example's `loadShaderProgram()` function, and how this function -// loads a module for reflection purposes. -// -RefPtr<ShaderModule> loadShaderModule(IRenderer* renderer, char const* inputPath) -{ - auto slangSession = getSlangSession(); - SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession); - - // When *loading* the shader library, we will request that concrete - // kernel code *not* be generated, because the module might have - // unspecialized generic parameters. Instead, we will generate kernels - // on demand at runtime. - // - spSetCompileFlags( - slangRequest, - SLANG_COMPILE_FLAG_NO_CODEGEN); - - // The main logic for specifying target information and loading source - // code is the same as before with the notable change that we are *not* - // specifying specific vertex/fragment entry points to compile here. - // - // Instead, the `[shader(...)]` attributes used in `shaders.slang` will - // identify the entry points in the shader library to the compiler with - // specific action needing to be taken in the application. - // - int targetIndex = spAddCodeGenTarget(slangRequest, SLANG_DXBC); - spSetTargetProfile(slangRequest, targetIndex, spFindProfile(slangSession, "sm_4_0")); - int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr); - spAddTranslationUnitSourceFile(slangRequest, translationUnitIndex, inputPath); - int compileErr = spCompile(slangRequest); - if(auto diagnostics = spGetDiagnosticOutput(slangRequest)) - { - reportError("%s", diagnostics); - } - if(compileErr) - { - spDestroyCompileRequest(slangRequest); - spDestroySession(slangSession); - return nullptr; - } - auto slangReflection = (slang::ShaderReflection*) spGetReflection(slangRequest); - - // We will not destroy the Slang compile request here, because we want to - // keep it around to service reflection quries made from the application code. - // - RefPtr<ShaderModule> module = new ShaderModule(); - module->renderer = renderer; - module->inputPath = inputPath; - module->slangRequest = slangRequest; - module->slangReflection = slangReflection; - return module; -} - -// Once a shader moduel has been loaded, it is possible to look up -// individual entry points by their name to get reflection information, -// including the stage for which the entry point was compiled. -// -// As with `ShaderModule` above, the `EntryPoint` type is the application's -// wrapper around a Slang entry point. In this case it caches the -// identity of the target stage as encoded for the graphics API. -// -struct EntryPoint : RefObject -{ - // Name of the entry point function - std::string name; - - // Stage targetted by the entry point (Slang version) - SlangStage slangStage; - - // Stage targetted by the entry point (graphics API version) - gfx::StageType apiStage; -}; -// -// Loading an entry point from a module is a straightforward -// application of the Slang reflection API. -// -RefPtr<EntryPoint> loadEntryPoint( - ShaderModule* module, - char const* name) -{ - auto slangReflection = module->slangReflection; - - // Look up the Slang entry point based on its name, and bail - // out with an error if it isn't found. - // - auto slangEntryPoint = slangReflection->findEntryPointByName(name); - if(!slangEntryPoint) return nullptr; - - // Extract the stage of the entry point using the Slang API, - // and then try to map it to the corresponding stage as - // exposed by the graphics API. - // - auto slangStage = slangEntryPoint->getStage(); - StageType apiStage = StageType::Unknown; - switch(slangStage) - { - default: - return nullptr; - - case SLANG_STAGE_VERTEX: apiStage = gfx::StageType::Vertex; break; - case SLANG_STAGE_FRAGMENT: apiStage = gfx::StageType::Fragment; break; - } - - // Allocate an application object to hold on to this entry point - // so that we can use it in later specialization steps. - // - RefPtr<EntryPoint> entryPoint = new EntryPoint(); - entryPoint->name = name; - entryPoint->slangStage = slangEntryPoint->getStage(); - entryPoint->apiStage = apiStage; - return entryPoint; -} - -// In this application a `Program` represents a combination of entry -// points that will be used together (e.g., matching vertex and fragment -// entry points). -// -// Along with the entry points themselves, the `Program` object will -// cache information gleaned from Slang's reflection interface. Notably: -// -// * The number of `ParameterBlock`s that the program uses -// * Information about generic (type) parameters -// -struct Program : RefObject -{ - // The shader module that the program was loaded from. - RefPtr<ShaderModule> shaderModule; - - // The entry points that comprise the program - // (e.g., both a vertex and a fragment entry point). - std::vector<RefPtr<EntryPoint>> entryPoints; - - // The number of parameter blocks that are used by the shader - // program. This will be used by our rendering code later to - // decide how many descriptor set bindings should affect - // specialization/execution using this program. - // - int parameterBlockCount; - - // We will store information about the generic (type) parameters - // of the program. In particular, for each generic parameter - // we are going to find a parameter block that uses that - // generic type parameter. - // - // E.g., given input code like: - // - // type_param A; - // type_param B; - // - // ParameterBlock<B> x; // block 0 - // ParameterBlock<Foo> y; // block 1 - // ParameterBlock<A> z; // block 2 - // - // We would have two `GenericParam` entries. The first one, - // for `A`, would store a `parameterBlockIndex` of `2`, because - // `A` is used as the type of the `x` parameter block. - // - // This information will be used later when we want to specialize - // shader code, because if `z` is bound using a `ParameterBlock<Bar>` - // then we can infer that `A` should be bound to `Bar`. - // - struct GenericParam - { - int parameterBlockIndex; - }; - std::vector<GenericParam> genericParams; -}; -// -// As with entry points, loading a program is done with -// the help of Slang's reflection API. -// -RefPtr<Program> loadProgram( - ShaderModule* module, - int entryPointCount, - const char* const* entryPointNames) -{ - auto slangReflection = module->slangReflection; - - RefPtr<Program> program = new Program(); - program->shaderModule = module; - - // We will loop over the entry point names that were requested, - // loading each and adding it to our program. - // - for(int ee = 0; ee < entryPointCount; ++ee) - { - auto entryPoint = loadEntryPoint(module, entryPointNames[ee]); - if(!entryPoint) - return nullptr; - program->entryPoints.push_back(entryPoint); - } - - // Next, we will look at the reflection information to see how - // many generic type parameters were declared, and allocate - // space in the `genericParams` array for them. - // - // We don't yet have enough information to fill in the - // `parameterBlockIndex` field. - // - auto genericParamCount = slangReflection->getTypeParameterCount(); - for(unsigned int pp = 0; pp < genericParamCount; ++pp) - { - auto slangGenericParam = slangReflection->getTypeParameterByIndex(pp); - - Program::GenericParam genericParam = {}; - program->genericParams.push_back(genericParam); - } - - // We want to specialize our shaders based on what gets bound - // in parameter blocks, so we will scan the shader parameters - // looking for `ParameterBlock<G>` where `G` is one of our - // generic type parameters. - // - // We do this by iterating over *all* the global shader paramters, - // and looking for those that happen to be parameter blocks, and - // of those the ones where the "element type" of the parameter block - // is a generic type parameter. - // - auto paramCount = slangReflection->getParameterCount(); - int parameterBlockCounter = 0; - for(unsigned int pp = 0; pp < paramCount; ++pp) - { - auto slangParam = slangReflection->getParameterByIndex(pp); - - // Is it a parameter block? If not, skip it. - if(slangParam->getType()->getKind() != slang::TypeReflection::Kind::ParameterBlock) - continue; - - // Okay, we've found another parameter block, so we can compute its zero-based index. - int parameterBlockIndex = parameterBlockCounter++; - - // Get the element type of the parameter block, and if it isn't a generic type - // parameter, then skip it. - auto slangElementTypeLayout = slangParam->getTypeLayout()->getElementTypeLayout(); - if(slangElementTypeLayout->getKind() != slang::TypeReflection::Kind::GenericTypeParameter) - continue; - - // At this point we've found a `ParameterBlock<G>` where `G` is a `type_param`, - // so we can store the index of the parameter block back into our array of - // generic type parameter info. - // - auto genericParamIndex = slangElementTypeLayout->getGenericParamIndex(); - program->genericParams[genericParamIndex].parameterBlockIndex = parameterBlockIndex; - } - - // The above loop over the global shader parameters will have found all the - // parameter blocks that were specified in the shader code, so now we know - // how many parameter blocks are expected to be bound when this program is used. - // - program->parameterBlockCount = parameterBlockCounter; - - return program; -} -// -// As a convenience, we will define a simple wrapper around `loadProgram` for the case -// where we have just two entry points, since that is what the application actually uses. -// -RefPtr<Program> loadProgram(ShaderModule* module, char const* entryPoint0, char const* entryPoint1) -{ - char const* entryPointNames[] = { entryPoint0, entryPoint1 }; - return loadProgram(module, 2, entryPointNames); -} - -// The `ParameterBlock<T>` type is supported by the Slang language and compiler, -// but it is up to each application to map it down to whatever graphics API -// abstraction is most fitting. -// -// For our application, a parameter block will be implemented as a combination -// of Slang type reflection information (to determine the layout) plus a -// graphics API descriptor set object. -// -// Note: the example graphics API abstraction we are using exposes descriptor sets -// similar to those in Vulkan, and then maps these down to efficient alternatives -// on other APIs including D3D12, D3D11, and OpenGL. -// -// Before we dive into the definition of the application's `ParameterBlock` type, -// we will start with some underlying types. -// -// Every parameter block is allocated based on a particular layout, and we -// can share the same layout across multiple blocks: -// -struct ParameterBlockLayout : RefObject -{ - // The graphics API device that should be used to allocate parameter - // block instances. - // - Slang::ComPtr<gfx::IRenderer> renderer; - - // The name of the type, as it appears in Slang code. - // - std::string typeName; - - // The Slang type layout information that will be used to decide - // how much space is needed in instances of this layout. - // - // If the user declares a `ParameterBlock<Batman>` parameter, then - // this will be the type layout information for `Batman`. - // - slang::TypeLayoutReflection* slangTypeLayout; - - // The size of the "primary" constant buffer that will hold any - // "ordinary" (not-resource) fields in the `slangTypeLayout` above. - // - size_t primaryConstantBufferSize; - - // API-specific layout information computes from `slangTypelayout`. - // - ComPtr<gfx::IDescriptorSetLayout> descriptorSetLayout; -}; -// -// A parameter block layout can be computed for any `struct` type -// declared in the user's shade code. We extract the relevant -// information from the type using the Slang reflection API. -// -RefPtr<ParameterBlockLayout> getParameterBlockLayout( - ShaderModule* module, - char const* name) -{ - auto slangReflection = module->slangReflection; - auto renderer = module->renderer; - - // Look up the type with the given name, and bail out - // if no such type is found in the module. - // - auto type = slangReflection->findTypeByName(name); - if(!type) return nullptr; - - // Request layout information for the type. Note that a single - // type might be laid out differently for different compilation - // targets, or based on how it is used (e.g., as a `cbuffer` - // field vs. in a `StructuredBuffer`). - // - auto typeLayout = slangReflection->getTypeLayout(type); - if(!typeLayout) return nullptr; - - // If the type that is going in the parameter block has - // any ordinary data in it (as opposed to resources), then - // a constant buffer will be needed to hold that data. - // - // In turn any resource parameters would need to go into - // the descriptor set *after* this constant buffer. - // - size_t primaryConstantBufferSize = typeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM); - - // We need to use the Slang reflection information to - // create a graphics-API-level descriptor-set layout that - // is compatible with the original declaration. - // - std::vector<gfx::IDescriptorSetLayout::SlotRangeDesc> slotRanges; - - // If the type has any ordinary data, then the descriptor set - // will need a constant buffer to be the first thing it stores. - // - // Note: for a renderer only targetting D3D12, it might make - // sense to allocate this "primary" constant buffer as a root - // descriptor instead of inside the descriptor set (or at least - // do this *if* there are no non-uniform parameters). Policy - // decisions like that are up to the application, not Slang. - // This example application just does something simple. - // - if(primaryConstantBufferSize) - { - slotRanges.push_back( - gfx::IDescriptorSetLayout::SlotRangeDesc( - gfx::DescriptorSlotType::UniformBuffer)); - } - - // Next, the application will recursively walk - // the structure of `typeLayout` to figure out what resource - // binding ranges are required for the target API. - // - // TODO: This application doesn't yet use any resource parameters, - // so we are skipping this step, but it is obviously needed - // for a fully fleshed-out example. - - // Now that we've collected the graphics-API level binding - // information, we can construct a graphics API descriptor set - // layout. - gfx::IDescriptorSetLayout::Desc descriptorSetLayoutDesc; - descriptorSetLayoutDesc.slotRangeCount = slotRanges.size(); - descriptorSetLayoutDesc.slotRanges = slotRanges.data(); - auto descriptorSetLayout = renderer->createDescriptorSetLayout(descriptorSetLayoutDesc); - if(!descriptorSetLayout) return nullptr; - - RefPtr<ParameterBlockLayout> parameterBlockLayout = new ParameterBlockLayout(); - parameterBlockLayout->renderer = renderer; - parameterBlockLayout->primaryConstantBufferSize = primaryConstantBufferSize; - parameterBlockLayout->typeName = name; - parameterBlockLayout->slangTypeLayout = typeLayout; - parameterBlockLayout->descriptorSetLayout = descriptorSetLayout; - return parameterBlockLayout; -} -// -// In some cases, we may want to create a parameter block based -// on a *generic* type in the shader code (e.g., `LightPair<A,B>`). -// -// The current Slang API re-uses the `findTypeByName()` operation to -// support specialization of types, by allowing the user to pass in -// the string name of a sepcialized type and have the Slang runtime -// system parse it. -// -// Note: a future version of the Slang API may streamline this operation -// so that less application code is needed. -// -// In order to construct the string name of a type like `LightArray<X,3>` -// we need a uniform encoding of the generic *arguments* `X` and `3`. -// We use the `SpecializationArg` for this: -// -struct SpecializationArg -{ - // A `SpecializationArg` is just a thing wrapper around a string, - // with support for implicit conversions from the values we might - // use as specialization arguments. - - SpecializationArg(Int val) - { - str = std::to_string(val); - } - SpecializationArg(RefPtr<ParameterBlockLayout> layout) - { - str = layout->typeName; - } - - std::string str; -}; -// -// Now, given the name of a type to specialize and its specialization -// arguments, we can easily construct the string name of the specialized -// type and defer to the existing `getParameterBlockLayout()`. -// -RefPtr<ParameterBlockLayout> getSpecializedParameterBlockLayout( - ShaderModule* module, - char const* name, - Int argCount, - SpecializationArg const* args) -{ - std::stringstream stream; - stream << name << "<"; - for (Int aa = 0; aa < argCount; ++aa) - { - if (aa != 0) stream << ","; - stream << args[aa].str; - } - stream << ">"; - - std::string specializedName = stream.str(); - return getParameterBlockLayout(module, specializedName.c_str()); -} -RefPtr<ParameterBlockLayout> getSpecializedParameterBlockLayout( - ShaderModule* module, - char const* name, - SpecializationArg const& arg0, - SpecializationArg const& arg1) -{ - SpecializationArg args[] = { arg0, arg1 }; - return getSpecializedParameterBlockLayout(module, name, 2, args); -} - -// In order to allow parameter blocks to be filled in conveniently, -// we will introduce a helper type for "encoding" parameter blocks -// (those familiar with the Metal API may recognize a similarity -// to the `MTLArgumentEncoder` type). -// -struct ParameterBlockEncoder -{ - // The parameter block being filled in (if this is - // a "top-level" encoder. - // - struct ParameterBlock* parameterBlock = nullptr; - - // A top-level encoder will unmap the underlying constant - // buffer (if any) when it goes out of scope. - // - void finishEncoding(); - - // The underlying descriptor set being filled in. - // - gfx::IDescriptorSet* descriptorSet = nullptr; - - // The Slang type information for the part of the - // block that we are filling in. This might be the - // type stored in the whole block, the type of a single - // field, or anything in between. - // - slang::TypeLayoutReflection* slangTypeLayout = nullptr; - - // A pointer to the uniform data for the (sub)block - // being filled in, as well as offsets for the resource - // binding ranges. - // - char* uniformData = nullptr; - Int rangeOffset = 0; - Int rangeArrayIndex = 0; - - // Assuming we have an encoder for a `struct` type, - // return an encoder for a single field by its index. - // - ParameterBlockEncoder beginField(Int fieldIndex) - { - assert(slangTypeLayout->getKind() == slang::TypeReflection::Kind::Struct); - - auto slangField = slangTypeLayout->getFieldByIndex((unsigned int)fieldIndex); - auto fieldUniformOffset = slangField->getOffset(); - - // TODO: this type needs to be extended to handle resource fields. - size_t fieldRangeOffset = 0; - - ParameterBlockEncoder subEncoder; - subEncoder.descriptorSet = descriptorSet; - subEncoder.slangTypeLayout = slangField->getTypeLayout(); - subEncoder.uniformData = uniformData + fieldUniformOffset; - subEncoder.rangeOffset = rangeOffset + fieldRangeOffset; - subEncoder.rangeArrayIndex = rangeArrayIndex; - return subEncoder; - } - - // Assuming we have an encoder for an array type, return an - // encoder for an element of that array. - // - ParameterBlockEncoder beginArrayElement(Int index) - { - assert(slangTypeLayout->getKind() == slang::TypeReflection::Kind::Array); - - auto uniformStride = slangTypeLayout->getElementStride(slang::ParameterCategory::Uniform); - auto slangElementTypeLayout = slangTypeLayout->getElementTypeLayout(); - - ParameterBlockEncoder subEncoder; - subEncoder.descriptorSet = descriptorSet; - subEncoder.slangTypeLayout = slangElementTypeLayout; - subEncoder.uniformData = uniformData + index * uniformStride; - subEncoder.rangeOffset = rangeOffset; - subEncoder.rangeArrayIndex = index; - return subEncoder; - } - - // Write uniform data into this encoder. - // - void writeUniform(const void* data, size_t dataSize) - { - memcpy(uniformData, data, dataSize); - } - template<typename T> - void write(T const& value) - { - writeUniform(&value, sizeof(value)); - } - - // As a convenience, create a sub-encoder for a single field, - // and write a single value into it. - // - template<typename T> - void writeField(Int fieldIndex, T const& value) - { - beginField(fieldIndex).write(value); - } -}; - -// With the layout and encoder types dealt with, we are now -// prepared to -// A `ParameterBlock` abstracts over the allocated storage -// for a descriptor set, based on some `ParameterBlockLayout` -// -struct ParameterBlock : RefObject -{ - // The graphics API device used to allocate this block. - Slang::ComPtr<gfx::IRenderer> renderer; - - // The associated parameter block layout. - RefPtr<ParameterBlockLayout> layout; - - // The (optional) constant buffer that holds the values - // for any ordinay fields. This will be null if - // `layout->primaryConstantBufferSize` is zero. - ComPtr<IBufferResource> primaryConstantBuffer; - - // The graphics-API descriptor set that provides storage - // for any resource fields. - ComPtr<gfx::IDescriptorSet> descriptorSet; - - ParameterBlockEncoder beginEncoding(); -}; - -// Allocating a parameter block is mostly a matter of allocating -// the required graphics API objects. -// -RefPtr<ParameterBlock> allocateParameterBlockImpl( - ParameterBlockLayout* layout) -{ - auto renderer = layout->renderer; - - // A descriptor set is then used to provide the storage for all - // resource parameters (including the primary constant buffer, if any). - // - auto descriptorSet = renderer->createDescriptorSet( - layout->descriptorSetLayout, gfx::IDescriptorSet::Flag::Transient); - - // If the parameter block has any ordinary data, then it requires - // a "primary" constant buffer to hold that data. - // - ComPtr<gfx::IBufferResource> primaryConstantBuffer = nullptr; - if(auto primaryConstantBufferSize = layout->primaryConstantBufferSize) - { - gfx::IBufferResource::Desc bufferDesc; - bufferDesc.init(primaryConstantBufferSize); - bufferDesc.setDefaults(gfx::IResource::Usage::ConstantBuffer); - bufferDesc.cpuAccessFlags = gfx::IResource::AccessFlag::Write; - primaryConstantBuffer = renderer->createBufferResource( - gfx::IResource::Usage::ConstantBuffer, - bufferDesc); - - // The primary constant buffer will always be the first thing - // stored in the descriptor set for a parameter block. - // - descriptorSet->setConstantBuffer(0, 0, primaryConstantBuffer); - } - - // Now that we've allocated the graphics API objects, we can just - // allocate our application-side wrapper object to tie everything - // together. - // - RefPtr<ParameterBlock> parameterBlock = new ParameterBlock(); - parameterBlock->renderer = renderer; - parameterBlock->layout = layout; - parameterBlock->primaryConstantBuffer = primaryConstantBuffer; - parameterBlock->descriptorSet = descriptorSet; - return parameterBlock; -} - -// A full-featured high-performance application would likely draw -// a distinction between "persistent" parameter blocks that are -// filled in once and then used over many frames, and "transient" -// blocks that are allocated, filled in, and discarded within -// a single frame. -// -// These two cases warrant very different allocation strategies, -// but for now we are using the same logic in both cases. -// -RefPtr<ParameterBlock> allocatePersistentParameterBlock( - ParameterBlockLayout* layout) -{ - return allocateParameterBlockImpl(layout); -} -RefPtr<ParameterBlock> allocateTransientParameterBlock( - ParameterBlockLayout* layout) -{ - return allocateParameterBlockImpl(layout); -} - -// In order to fill in a parameter block, the application -// will create an encoder pointing at the mapped uniform -// data for the block: -// -ParameterBlockEncoder ParameterBlock::beginEncoding() -{ - ParameterBlockEncoder encoder; - encoder.parameterBlock = this; - encoder.descriptorSet = descriptorSet; - encoder.slangTypeLayout = layout->slangTypeLayout; - encoder.uniformData = primaryConstantBuffer ? - (char*) renderer->map( - primaryConstantBuffer, - MapFlavor::WriteDiscard) - : nullptr; - encoder.rangeOffset = 0; - encoder.rangeArrayIndex = 0; - return encoder; -} - -void ParameterBlockEncoder::finishEncoding() -{ - if (parameterBlock && uniformData) - { - parameterBlock->renderer->unmap( - parameterBlock->primaryConstantBuffer); - } -} - -// The core of our application's rendering abstraction is -// the notion of an "effect," which ties together a particular -// set of shader entry points (as a `Program`), with graphics -// API state objects for the fixed-function parts of the pipeline. -// -// Note that the program here is an *unspecialized* program, -// which might have unbound global `type_param`s. Thus the -// `Effect` type here is not one-to-one with a "pipeline state -// object," because the same effect could be used to instantiate -// multiple pipeline state objects based on how things get -// specialized. -// -struct Effect : RefObject -{ - // The shader program entry point(s) to execute - RefPtr<Program> program; - - // Additional state corresponding to the data needed - // to create a graphics-API pipeline state object. - ComPtr<gfx::IInputLayout> inputLayout; - Int renderTargetCount; -}; - -// In order to render using the `Effect` abstraction, our -// application will be creating various specialized -// shader kernels and pipeline states on-demand. -// -// We'll start with the representation of a specialized -// "variant" of an effect. -// -struct EffectVariant : RefObject -{ - // The graphics API pipeline layout and state - // that need to be bound in order to use this - // effect. - // - ComPtr<gfx::IPipelineLayout> pipelineLayout; - ComPtr<gfx::IPipelineState> pipelineState; -}; -// -// A specialized variant is created based on a base effect -// and the types that will be bound to its parameter blocks. -// -RefPtr<EffectVariant> createEffectVaraint( - Effect* effect, - UInt parameterBlockCount, - ParameterBlockLayout* const* parameterBlockLayouts, - IFramebufferLayout* framebufferLayout) -{ - // One note to make at the very start is that the creation - // of a specialized variant is based on the *layout* of - // the parameter blocks in use and not on the particular - // parameter blocks themselves. This is important because - // it means that, e.g., two materials that use the same code, - // but different parameter values (different textures, colors, - // etc.) do *not* require switching between different - // shader code or specialized PSOs. - - // We'll start by extracting some of the pieces of - // information taht we need into local variables, - // just to simplify the remaining code. - // - auto program = effect->program; - auto shaderModule = program->shaderModule; - auto renderer = shaderModule->renderer; - - // Our specialized effect is going to need a few things: - // - // 1. A specialized pipeline layout, based on the layout - // of the bound parameter blocks. - // - // 2. Specialized shader kernels, based on "plugging in" - // the parameter block types for generic type parameters - // as needed. - // - // 3. A specialized pipeline state object that ties the - // above items together with the fixed-function state - // already specified in the effect. - // - // We will now go through these steps in order. - - // (1) The pipline layout (aka D3D12 "root signature") will - // be determined based on the descriptor-set layouts - // already cached in the given parameter block layouts. - // - std::vector<IPipelineLayout::DescriptorSetDesc> descriptorSets; - for(UInt pp = 0; pp < parameterBlockCount; ++pp) - { - descriptorSets.emplace_back( - parameterBlockLayouts[pp]->descriptorSetLayout); - } - IPipelineLayout::Desc pipelineLayoutDesc; - pipelineLayoutDesc.renderTargetCount = 1; - pipelineLayoutDesc.descriptorSetCount = descriptorSets.size(); - pipelineLayoutDesc.descriptorSets = descriptorSets.data(); - auto pipelineLayout = renderer->createPipelineLayout(pipelineLayoutDesc); - - // (2) The final shader kernels to bind will be computed - // from the kernels we extracted into an application `EntryPoint` - // plus the types of the bound paramter blocks, as needed. - // - // We will "infer" a type argument for each of the generic - // parameters of our shader program by looking for a - // parameter block that is declared using that generic - // type. - // - std::vector<const char*> genericArgs; - for(auto gp : program->genericParams) - { - int parameterBlockIndex = gp.parameterBlockIndex; - auto typeName = parameterBlockLayouts[parameterBlockIndex]->typeName.c_str(); - genericArgs.push_back(typeName); - } - - // Now that we are ready to generate specialized shader code, - // we wil invoke the Slang compiler again. This time we leave - // full code generation turned on, and we also specify the - // entry points that we want explicitly (so that we don't - // generate code for any other entry points). - // - auto slangSession = getSlangSession(); - SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession); - int targetIndex = spAddCodeGenTarget(slangRequest, SLANG_DXBC); - spSetTargetProfile(slangRequest, targetIndex, spFindProfile(slangSession, "sm_4_0")); - int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr); - spAddTranslationUnitSourceFile(slangRequest, translationUnitIndex, program->shaderModule->inputPath.c_str()); - - // Because our shader code uses global generic parameters for - // specialization, we need to specify the concrete argument - // types for the compiler to use when generating code. - // - spSetGlobalGenericArgs( - slangRequest, - int(genericArgs.size()), - genericArgs.data()); - - // Next we tell the Slang compiler about all of the entry points - // we plan to use. - // - const int entryPointCount = int(program->entryPoints.size()); - for(int ii = 0; ii < entryPointCount; ++ii) - { - auto entryPoint = program->entryPoints[ii]; - spAddEntryPoint( - slangRequest, - translationUnitIndex, - entryPoint->name.c_str(), - entryPoint->slangStage); - } - - // We expect compilation to go through without a hitch, because the - // code was already statically checked back in `loadShaderModule()`. - // It is still possible for errors to arise if, e.g., the application - // tries to specialize code based on a type that doesn't implement - // a required interface. - // - int compileErr = spCompile(slangRequest); - if(auto diagnostics = spGetDiagnosticOutput(slangRequest)) - { - reportError("%s", diagnostics); - } - if(compileErr) - { - spDestroyCompileRequest(slangRequest); - assert(!"unexected"); - return nullptr; - } - - // Once compilation is done we can extract the kernel code - // for each of the entry points, and set them up for passing - // to the graphics APIs loading logic. - // - std::vector<ISlangBlob*> kernelBlobs; - std::vector<gfx::IShaderProgram::KernelDesc> kernelDescs; - for(int ii = 0; ii < entryPointCount; ++ii) - { - auto entryPoint = program->entryPoints[ii]; - - ISlangBlob* blob = nullptr; - spGetEntryPointCodeBlob(slangRequest, ii, 0, &blob); - - kernelBlobs.push_back(blob); - - IShaderProgram::KernelDesc kernelDesc; - - char const* codeBegin = (char const*) blob->getBufferPointer(); - char const* codeEnd = codeBegin + blob->getBufferSize(); - - kernelDesc.stage = entryPoint->apiStage; - kernelDesc.codeBegin = codeBegin; - kernelDesc.codeEnd = codeEnd; - - kernelDescs.push_back(kernelDesc); - } - - // Once we've extracted the "blobs" of compiled code, - // we are done with the Slang compilation request. - // - // Note that all of our reflection was performed on the unspecialized - // shader code at load time, but we know that information is still - // applicable to specialized kernels because of the guarantees - // the Slang compiler makes about type layout. - // - spDestroyCompileRequest(slangRequest); - - // We use the graphics API to load a program into the GPU - gfx::IShaderProgram::Desc programDesc = {}; - programDesc.pipelineType = gfx::PipelineType::Graphics; - programDesc.kernels = kernelDescs.data(); - programDesc.kernelCount = kernelDescs.size(); - auto specializedProgram = renderer->createProgram(programDesc); - - // Then we unload our "blobs" of kernel code once the graphics - // API is doen with their data. - // - for(auto blob : kernelBlobs) - { - blob->release(); - } - - // (3) We construct a full graphics API pipeline state - // object that combines our new program and pipeline layout - // with the other state objects from the `Effect`. - // - gfx::GraphicsPipelineStateDesc pipelineStateDesc = {}; - pipelineStateDesc.program = specializedProgram; - pipelineStateDesc.pipelineLayout = pipelineLayout; - pipelineStateDesc.inputLayout = effect->inputLayout; - pipelineStateDesc.framebufferLayout = framebufferLayout; - auto pipelineState = renderer->createGraphicsPipelineState(pipelineStateDesc); - - RefPtr<EffectVariant> variant = new EffectVariant(); - variant->pipelineLayout = pipelineLayout; - variant->pipelineState = pipelineState; - return variant; -} - -// A more advanced application might add logic to -// pre-populate the shader cache with shader variants -// that were compiled offline. -// -struct ShaderCache : RefObject -{ - struct VariantKey - { - Effect* effect; - UInt parameterBlockCount; - ParameterBlockLayout* parameterBlockLayouts[8]; - - // In order to be used as a hash-table key, our - // variant key representation must support - // equality comparison and a matching hashin function. - - bool operator==(VariantKey const& other) const - { - if(effect != other.effect) return false; - if(parameterBlockCount != other.parameterBlockCount) return false; - for( UInt ii = 0; ii < parameterBlockCount; ++ii ) - { - if(parameterBlockLayouts[ii] != other.parameterBlockLayouts[ii]) return false; - } - return true; - } - - Slang::HashCode getHashCode() const - { - auto hash = Slang::getHashCode(effect); - hash = Slang::combineHash(hash, Slang::getHashCode(parameterBlockCount)); - for( UInt ii = 0; ii < parameterBlockCount; ++ii ) - { - hash = Slang::combineHash(hash, Slang::getHashCode(parameterBlockLayouts[ii])); - } - return hash; - } - }; - - // The shader cache is mostly just a dictionary mapping - // variant keys to the associated variant, generated on-demand. - // - Slang::Dictionary<VariantKey, RefPtr<EffectVariant> > variants; - - // Getting a variant is just a matter of looking for an - // existing entry in the dictionary, and creating one - // on demand in case of a miss. - // - RefPtr<EffectVariant> getEffectVariant( - VariantKey const& key, - IFramebufferLayout* framebufferLayout) - { - RefPtr<EffectVariant> variant; - if(variants.TryGetValue(key, variant)) - return variant; - - variant = createEffectVaraint( - key.effect, - key.parameterBlockCount, - key.parameterBlockLayouts, - framebufferLayout); - - variants.Add(key, variant); - return variant; - } - - // We support clearign the shader cache, which can serve - // as a kind of "hot reload" action, because subsequent - // rendering work will need to re-compile shader variants - // from scratch. - // - void clear() - { - variants.Clear(); - } -}; - - -// In order to render using the `Effect` abstraction, our -// application will use its own rendering context type -// to manage the state that it is binding. This layer -// performs a small amount of shadowing on top of the -// underlying graphics API. -// -// Note: for the purposes of our examples the "graphcis API" -// in a cross-platform abstraction over multiple APIs, but -// we do not actually advocate that real applications should -// be built in terms of distinct layers for cross-platform -// GPU API abstraction and "effect" state management. -// -// A high-performance application built on top of this approach -// would instead implement the concepts like `ParameterBlock` -// and `RenderContext` on a per-API basis, making use of -// whatever is most efficeint on that API without any -// additional abstraction layers in between. -// -// We've done things differently in this example program in -// order to avoid getting bogged down in the specifics of -// any one GPU API. -// -// With that disclaimer out of the way, let's talk through -// the `RenderContext` type in this application. -// -struct RenderContext -{ -private: - // The `RenderContext` type is used to wrap the graphics - // API "context" or "command list" type for submission. - // Our current abstraction layer lumps this all together - // with the "device." - // - Slang::ComPtr<gfx::IRenderer> renderer; - - // We also retain a pointer to the shader cache, which - // will be used to implement lookup of the right - // effect variant to execute based on bound parameter - // blocks. - // - RefPtr<ShaderCache> shaderCache; - - // We will establish a small upper bound on how many - // parameter blocks can be used simultaneously. In - // practice, most shaders won't need more than about - // four parameter blocks, and attempting to use more - // than that under Vulkan can cause portability issues. - // - enum { kMaxParameterBlocks = 8 }; - - // The overall "state" of the rendering context consists of: - // - // * The currently selected "effect" - // * The parameter blocks that are used to specialize and - // provide parameters for that effects. - // - RefPtr<Effect> effect; - RefPtr<ParameterBlock> parameterBlocks[kMaxParameterBlocks]; - - // Along with the retained state above, we also store - // state in exactly the form required for looking up - // an effect variant in our shader cache, to minimize - // the work that needs to be done when looking up state. - // - ShaderCache::VariantKey variantKey; - - // When state gets changed, we track a few dirty flags rather than - // flush changes to the GPU right away. - - // Tracks whether any state has changed in a way that requires computing - // and binding a new GPU pipeline state object (PSO). - // - // E.g., changing the current effect would set this flag, but changing - // a parameter block binding to one with a new layout would also set the flag. - bool pipelineStateDirty = true; - - // The `minDirtyBlockBinding` flag tracks the lowest-numbered parameter - // block binding that needs to be flushed to the GPU. That is, if - // parameters blocks [0,N) have been bound to the GPU, and then the user - // tries to set block K, then the range [0,K-1) will be left alone, - // while the range [K,N) needs to be set again. - // - // This is an optimization that can be exploited on the Vulkan API - // (and potentially others) if switching pipeline layouts doesn't invalidate - // all currently-bound descriptor sets. - // - int minDirtyBlockBinding = 0; - - // Finally, we cache the specialized effect variant that has been - // most recently bound to the GPU state, so that we can use the - // information it stores (specifically the pipeline layout) when - // binding descriptor sets. - // - RefPtr<EffectVariant> currentEffectVariant; - -public: - // Initializing a render context just sets its pointer to the GPU API device - RenderContext( - gfx::IRenderer* renderer, - ShaderCache* shaderCache) - : renderer(renderer) - , shaderCache(shaderCache) - {} - - void setEffect( - Effect* inEffect) - { - // Bail out if nothing is changing. - if( inEffect == effect ) - return; - - effect = inEffect; - variantKey.effect = effect; - variantKey.parameterBlockCount = effect->program->parameterBlockCount; - - // Binding a new effect invalidates the current state object, since - // it will be a specialization of some other effect. - // - pipelineStateDirty = true; - } - - void setParameterBlock( - int index, - ParameterBlock* parameterBlock) - { - // Bail out if nothing is changing. - if(parameterBlock == parameterBlocks[index]) - return; - - parameterBlocks[index] = parameterBlock; - - // This parameter block needs to be bound to the GPU, and any - // parameter blocks after it in the list will also get re-bound - // (even if they haven't changed). This is a reasonable choice - // if parameter blocks are ordered based on expected frequency - // of update (so that lower-numbered blocks change less often). - // - minDirtyBlockBinding = std::min(index, minDirtyBlockBinding); - - // Next, check if the layout for the block we just bound - // is different than the one that was in place before, - // as stored in the "variant key" - // - auto layout = parameterBlock->layout; - if(layout.Ptr() == variantKey.parameterBlockLayouts[index]) - return; - - variantKey.parameterBlockLayouts[index] = layout; - - // Changing the layout of a parameter block (which includes - // the underlying Slang type) requires computing a new - // pipeline state object, because it may lead to differently - // specialized code being generated. - // - pipelineStateDirty = true; - } - - void flushState(IFramebufferLayout* framebufferLayout) - { - // The `flushState()` operation must be used by the application - // any time it binds a different effect or parameter block(s), - // to ensure that the GPU state is fully configured for rendering. - // It is thus important that this function do as little work - // as possible, especially in the common case where state - // doesn't actually need to change. - // - // The first check we do is to see if any change might require - // a different set of shader kernels. - // - if(pipelineStateDirty) - { - pipelineStateDirty = false; - - // Almost all of the logic for retrieving or creating - // a new pipeline state with specialized kernels is - // handled by our shader cache. - // - // In the common case, the desired variant will already - // be present in the cache, and this function returns - // without much effort. - // - auto variant = shaderCache->getEffectVariant(variantKey, framebufferLayout); - - // In order to adapt to a change in shader variant, - // we simply bind its PSO into the GPU state, and - // remember the variant we've selected. - // - renderer->setPipelineState(variant->pipelineState); - currentEffectVariant = variant; - } - - // Even if the current pipeline state was fine, we may need to - // bind one or more descriptor sets. We do this by walking - // from our lowest-numbered "dirty" set up to the number - // of sets expected by the current effect and binding them. - // - // If `minDirtyBlockBinding` is greater than or equal to the - // `parameterBlockCount` of the currently bound effect, then - // this will be a no-op. - // - // The common case in a tight drawing loop will be that only - // the last block will be dirty, and we will only execute - // one iteration of this loop. - // - auto program = effect->program; - auto parameterBlockCount = program->parameterBlockCount; - auto pipelineLayout = currentEffectVariant->pipelineLayout; - for(int ii = minDirtyBlockBinding; ii < parameterBlockCount; ++ii) - { - renderer->setDescriptorSet( - PipelineType::Graphics, - pipelineLayout, - ii, - parameterBlocks[ii]->descriptorSet); - } - minDirtyBlockBinding = parameterBlockCount; - } -}; - -// -// The above types represent a core set of abstractions for working -// with rendering effects and their parameters, while performing -// static specialization to maintain GPU efficiency. -// -// We will now turn our attention to application-side abstractions -// for lights and materials that will match up with our shader-side -// interface definitions. -// -// For example, our application code has a rudimentary material system, -// to match the `IMaterial` abstraction used in the shade code. -// -struct Material : RefObject -{ - // The key feature of a matrial in our application is that - // it can provide a parameter block that describes it and - // its parameters. The contents of the parameter block will - // be any colors, textures, etc. that the material needs, - // while the Slang type that was used to allocate the - // block will be an implementation of `IMaterial` that - // provides the evaluation logic for the material. - - // Each subclass of `Material` will provide a routine to - // create a parameter block of its chosen type/layout. - virtual RefPtr<ParameterBlock> createParameterBlock() = 0; - - // The parameter block for a material will be stashed here - // after it is created. - RefPtr<ParameterBlock> parameterBlock; -}; - -// For now we have only a single implementation of `Material`, -// which corresponds to the `SimpleMaterial` type in our shader -// code. -// -struct SimpleMaterial : Material -{ - glm::vec3 diffuseColor; - glm::vec3 specularColor; - float specularity; - - // When asked to create a parameter block, the `SimpleMaterial` - // type will allocate a block based on the corresponding - // shader type, and fill it in based on the data in the C++ - // object. - // - RefPtr<ParameterBlock> createParameterBlock() override - { - auto parameterBlockLayout = gParameterBlockLayout; - auto parameterBlock = allocatePersistentParameterBlock( - parameterBlockLayout); - - ParameterBlockEncoder encoder = parameterBlock->beginEncoding(); - encoder.writeField(0, diffuseColor); - encoder.writeField(1, specularColor); - encoder.writeField(2, specularity); - encoder.finishEncoding(); - - return parameterBlock; - } - - // We cache the corresponding parameter block layout for - // `SimpleMaterial` in a static variable so that we don't - // load it more than once. - // - static RefPtr<ParameterBlockLayout> gParameterBlockLayout; -}; -RefPtr<ParameterBlockLayout> SimpleMaterial::gParameterBlockLayout; - -// With the `Material` abstraction defined, we can go on to define -// the representation for loaded models that we will use. -// -// A `Model` will own vertex/index buffers, along with a list of meshes, -// while each `Mesh` will own a material and a range of indices. -// For this example we will be loading models from `.obj` files, but -// that is just a simple lowest-common-denominator choice. -// -struct Mesh : RefObject -{ - RefPtr<Material> material; - int firstIndex; - int indexCount; -}; -struct Model : RefObject -{ - typedef ModelLoader::Vertex Vertex; - - ComPtr<IBufferResource> vertexBuffer; - ComPtr<IBufferResource> indexBuffer; - PrimitiveTopology primitiveTopology; - int vertexCount; - int indexCount; - std::vector<RefPtr<Mesh>> meshes; -}; -// -// Loading a model from disk is done with the help of some utility -// code for parsing the `.obj` file format, so that the application -// mostly just registers some callbacks to allocate the objects -// used for its representation. -// -RefPtr<Model> loadModel( - IRenderer* renderer, - char const* inputPath, - ModelLoader::LoadFlags loadFlags = 0, - float scale = 1.0f) -{ - // The model loading interface using a C++ interface of - // callback functions to handle creating the application-specific - // representation of meshes, materials, etc. - // - struct Callbacks : ModelLoader::ICallbacks - { - void* createMaterial(MaterialData const& data) override - { - SimpleMaterial* material = new SimpleMaterial(); - material->diffuseColor = data.diffuseColor; - material->specularColor = data.specularColor; - material->specularity = data.specularity; - - material->parameterBlock = material->createParameterBlock(); - - return material; - } - - void* createMesh(MeshData const& data) override - { - Mesh* mesh = new Mesh(); - mesh->firstIndex = data.firstIndex; - mesh->indexCount = data.indexCount; - mesh->material = (Material*)data.material; - return mesh; - } - - void* createModel(ModelData const& data) override - { - Model* model = new Model(); - model->vertexBuffer = data.vertexBuffer; - model->indexBuffer = data.indexBuffer; - model->primitiveTopology = data.primitiveTopology; - model->vertexCount = data.vertexCount; - model->indexCount = data.indexCount; - - int meshCount = data.meshCount; - for (int ii = 0; ii < meshCount; ++ii) - model->meshes.push_back((Mesh*)data.meshes[ii]); - - return model; - } - }; - Callbacks callbacks; - - // We instantiate a model loader object and then use it to - // try and load a model from the chosen path. - // - ModelLoader loader; - loader.renderer = renderer; - loader.loadFlags = loadFlags; - loader.scale = scale; - loader.callbacks = &callbacks; - Model* model = nullptr; - if (SLANG_FAILED(loader.load(inputPath, (void**)&model))) - { - log("failed to load '%s'\n", inputPath); - return nullptr; - } - - return model; -} - -// Along with materials, our application needs to be able to represent -// multiple light sources in the scene. For this task we will use a C++ -// inheritance hierarchy rooted at `Light` to match the `ILight` -// interface in Slang. -// -// Unlike how materials are currently being handled, we will use a -// quick-and-dirty "RTTI" system for lights to allow some of the application -// code to abstract over particular light types. -// -struct Light; -struct LightType -{ - // A light type needs to know both the name of the type (e.g., so that - // we can load shader code), and must also provide a factory function - // to create lights on demand (e.g., when the user requests that one - // be added in a UI). - // - char const* name; - Light* (*createLight)(); -}; -// -// The following is some crud to bootstrap the rudimentary RTTI system -// for lights. Each concrete subclass of `Light` needs to use the -// `DEFINE_LIGHT_TYPE` macro to set up its RTTI info. -// -template<typename T> -struct LightTypeImpl -{ - static LightType type; - static Light* create() { return (Light*)(new T); } -}; -#define DEFINE_LIGHT_TYPE(NAME) \ - LightType LightTypeImpl<NAME>::type = { #NAME, &LightTypeImpl<NAME>::create }; -template<typename T> -LightType* getLightType() -{ - return &LightTypeImpl<T>::type; -} - -struct Light : RefObject -{ - // A light must be able to return its type information. - virtual LightType* getType() = 0; - - // A light must be able to write a representation of itself into - // a parameter block, or a part of one. - virtual void fillInParameterBlock(ParameterBlockEncoder& encoder) = 0; - - // For this application, a light must be able to present a user - // interface for people to modify its properties. - virtual void doUI() = 0; -}; - -// We will provide two nearly trivial implementations of `Light` for now, -// to show the kind of application code needed to line up with the corresponding -// types defined in the Slang shader code for this application. - -struct DirectionalLight : Light -{ - glm::vec3 direction = normalize(glm::vec3(1)); - glm::vec3 color = glm::vec3(1); - float intensity = 1; - - LightType* getType() override { return getLightType<DirectionalLight>(); }; - - void fillInParameterBlock(ParameterBlockEncoder& encoder) override - { - encoder.writeField(0, direction); - encoder.writeField(1, color*intensity); - } - - void doUI() override - { - if (ImGui::SliderFloat3("direction", &direction[0], -1, 1)) - { - direction = normalize(direction); - } - ImGui::ColorEdit3("color", &color[0]); - ImGui::DragFloat("intensity", &intensity, 1.0f, 0.0f, 10000.0f, "%.3f", 2.0f); - } -}; -DEFINE_LIGHT_TYPE(DirectionalLight); - -struct PointLight : Light -{ - glm::vec3 position = glm::vec3(0); - glm::vec3 color = glm::vec3(1); - float intensity = 10; - - LightType* getType() override { return getLightType<PointLight>(); }; - - void fillInParameterBlock(ParameterBlockEncoder& encoder) override - { - encoder.writeField(0, position); - encoder.writeField(1, color*intensity); - } - - void doUI() override - { - ImGui::DragFloat3("position", &position[0], 0.1f); - ImGui::ColorEdit3("color", &color[0]); - ImGui::DragFloat("intensity", &intensity, 1.0f, 0.0f, 10000.0f, "%.3f", 2.0f); - } -}; -DEFINE_LIGHT_TYPE(PointLight); - -// Rendering is usually done with collections of lights rather than single -// lights. This application will use a concept of "light environments" to -// group together lights for rendering. -// -// We want to be *able* to specialize our shader code based on the particular -// types of lights in a scene, but we also do not want to over-specialize -// and, e.g., use differnt specialized shaders for a scene with 99 point -// lights vs. 100. -// -// This particular application will use a notion of a "layout" for a lighting -// environment, which specifies the allowed types of lights, and the maximum -// number of lights of each type. Different lighting environment layouts -// will yield different specialized code. - -struct LightEnvLayout : public RefObject -{ - // Our lighting environment layout will track layout - // information for several different arrays: one - // for each supported light type. - // - struct LightArrayLayout : RefObject - { - LightType* type; - RefPtr<ParameterBlockLayout> lightLayout; - RefPtr<ParameterBlockLayout> arrayLayout; - Int maximumCount = 0; - }; - RefPtr<ShaderModule> module; - std::vector<RefPtr<LightArrayLayout>> lightArrayLayouts; - std::map<LightType*, Int> mapLightTypeToArrayIndex; - - LightEnvLayout(ShaderModule* module) - : module(module) - {} - - void addLightType(LightType* type, Int maximumCount) - { - Int arrayIndex = (Int)lightArrayLayouts.size(); - RefPtr<LightArrayLayout> layout = new LightArrayLayout(); - layout->type = type; - layout->lightLayout = ::getParameterBlockLayout(module, type->name); - layout->maximumCount = maximumCount; - - // When the user adds a light type `X` to a light-env layout, - // we need to compute the corresponding Slang type and - // layout information to use. If only a single light is - // supported, this will just be the type `X`, while for - // any other count this will be a `LightArray<X, maximumCount>` - // - if (maximumCount <= 1) - { - layout->arrayLayout = layout->lightLayout; - } - else - { - layout->arrayLayout = getSpecializedParameterBlockLayout( - module, "LightArray", layout->lightLayout, maximumCount); - } - - lightArrayLayouts.push_back(layout); - mapLightTypeToArrayIndex.insert(std::make_pair(type, arrayIndex)); - } - template<typename T> - void addLightType(Int maximumCount) - { - addLightType(getLightType<T>(), maximumCount); - } - - Int getArrayIndexForType(LightType* type) - { - auto iter = mapLightTypeToArrayIndex.find(type); - if (iter != mapLightTypeToArrayIndex.end()) - return iter->second; - - return -1; - } - - // We will compute a parameter block layout for the - // whole lighting environment on demand, and then - // cache it thereafter. - // - RefPtr<ParameterBlockLayout> parameterBlockLayout; - RefPtr<ParameterBlockLayout> getParameterBlockLayout() - { - if (!parameterBlockLayout) - { - parameterBlockLayout = computeParameterBlockLayout(); - } - return parameterBlockLayout; - } - - RefPtr<ParameterBlockLayout> computeParameterBlockLayout() - { - // Given a lighting environment with N light types: - // - // L0, L1, ... LN - // - // We want to compute the Slang type: - // - // LightPair<L0, LightPair<L1, ... LightPair<LN-1, LN>>> - // - // This is most easily accomplished by doing a "fold" while - // walking the array in reverse order. - - RefPtr<ParameterBlockLayout> envLayout; - - auto arrayCount = lightArrayLayouts.size(); - for (size_t ii = arrayCount; ii--;) - { - auto arrayInfo = lightArrayLayouts[ii]; - RefPtr<ParameterBlockLayout> arrayLayout = arrayInfo->arrayLayout; - - if (!envLayout) - { - // The is the right-most entry, so it is the base case for our "fold" - envLayout = arrayLayout; - } - else - { - // Fold one entry: `envLayout = LightPair<a, envLayout>` - envLayout = getSpecializedParameterBlockLayout( - module, "LightPair", arrayLayout, envLayout); - } - } - - if (!envLayout) - { - // Handle the special case of *zero* light types. - envLayout = ::getParameterBlockLayout(module, "EmptyLightEnv"); - } - - return envLayout; - } -}; - -// A `LightEnv` follows the structure of a `LightEnvLayout`, -// and provides storage for zero or more lights of various -// different types (up to the limits imposed by the layout). -// -struct LightEnv : public RefObject -{ - // A light environment is always created from a fixed layout - // in this application, so the constructor allocates an array - // for the per-light-type data. - // - // A more complex example might dynamically determine the - // layout based on the number of lights of each type active - // in the scene, with some quantization applied to avoid - // generating too many shader specializations. - // - // Note: the kind of specialization going on here would also - // be applicable to a deferred or "forward+" renderer, insofar - // as it sets the bounds on the total set of lights for - // a scene/frame, while per-tile/-cluster light lists would - // probably just be indices into the global structure. - // - RefPtr<LightEnvLayout> layout; - LightEnv(RefPtr<LightEnvLayout> layout) - : layout(layout) - { - for (auto arrayLayout : layout->lightArrayLayouts) - { - RefPtr<LightArray> lightArray = new LightArray(); - lightArray->layout = arrayLayout; - lightArrays.push_back(lightArray); - } - } - - // For each light type, we track the layout information, - // plus the list of active lights of that type. - // - struct LightArray : RefObject - { - RefPtr<LightEnvLayout::LightArrayLayout> layout; - std::vector<RefPtr<Light>> lights; - }; - std::vector<RefPtr<LightArray>> lightArrays; - - RefPtr<LightArray> getArrayForType(LightType* type) - { - auto index = layout->getArrayIndexForType(type); - return lightArrays[index]; - } - - void add(RefPtr<Light> light) - { - auto array = getArrayForType(light->getType()); - array->lights.push_back(light); - } - - virtual void doUI() - { - if (ImGui::Button("Add")) - { - ImGui::OpenPopup("AddLight"); - } - if (ImGui::BeginPopup("AddLight")) - { - for (auto array : lightArrays) - { - if (ImGui::MenuItem( - array->layout->type->name, - nullptr, - nullptr, - array->lights.size() < (size_t)array->layout->maximumCount)) - { - auto light = array->layout->type->createLight(); - array->lights.push_back(light); - } - } - ImGui::EndPopup(); - } - - for (auto array : lightArrays) - { - auto lightCount = array->lights.size(); - auto maxLightCount = array->layout->maximumCount; - if (ImGui::TreeNode( - array.Ptr(), - "%s (%d/%d)", - array->layout->type->name, - (int)lightCount, - (int)maxLightCount)) - { - size_t lightCounter = 0; - for (auto light : array->lights) - { - size_t lightIndex = lightCounter++; - if (ImGui::TreeNode(light.Ptr(), "%d", (int)lightIndex)) - { - light->doUI(); - ImGui::TreePop(); - } - } - ImGui::TreePop(); - } - } - } - - // Because the lighting environment will often change between frames, - // we will not try to optimize for the case where it doesn't change, - // and will instead fill in a "transient" parameter block from - // scratch every frame. - // - RefPtr<ParameterBlock> createParameterBlock() - { - auto parameterBlockLayout = layout->getParameterBlockLayout(); - auto parameterBlock = allocateTransientParameterBlock(parameterBlockLayout); - - ParameterBlockEncoder encoder = parameterBlock->beginEncoding(); - fillInParameterBlock(encoder); - encoder.finishEncoding(); - - return parameterBlock; - } - void fillInParameterBlock(ParameterBlockEncoder& inEncoder) - { - // When filling in the parameter block for a lighting - // environment, we mostly follow the structure of - // the type that was computed by the `LightEnvLayout`: - // - // LightPair<A, LightPair<B, ... LightPair<Y, Z>>> - // - // we will keep `encoder` pointed at the "spine" of this - // structure (so at an element that represents a `LightPair`, - // except for the special case of the last item like `Z` above). - // - // For each light type, we will then encode the data as - // needed for the light type (`A` then `B` then ...) - // - auto encoder = inEncoder; - size_t lightTypeCount = lightArrays.size(); - for (size_t tt = 0; tt < lightTypeCount; ++tt) - { - // The encoder for the very last item will - // just be the one on the "spine" of the list. - auto lightTypeEncoder = encoder; - if (tt != lightTypeCount - 1) - { - // In the common case `encoder` is set up - // for writing to a `LightPair<X, Y>` so - // we ant to set up the `lightTypeEncoder` - // for writing to an `X` (which is the first - // field of `LightPair`, and then have - // `encoder` move on to the `Y` (the rest - // of the list of light types). - // - lightTypeEncoder = encoder.beginField(0); - encoder = encoder.beginField(1); - } - - auto& lightTypeArray = lightArrays[tt]; - size_t lightCount = lightTypeArray->lights.size(); - size_t maxLightCount = lightTypeArray->layout->maximumCount; - - // Recall that we are representing the data for a single - // light type `L` as either an instance of type `L` (if - // only a single light is supported), or as an instance - // of the type `LightArray<L,N>`. - // - if (maxLightCount == 1) - { - // This is the case where the maximu number of lights of - // the given type was set as one, so we just have a value - // of type `L`, and can tell the first light in our application-side - // array to encode itself into that location. - - if (lightCount > 0) - { - lightTypeArray->lights[0]->fillInParameterBlock(lightTypeEncoder); - } - else - { - // We really ought to zero out the entry in this case - // (under the assumption that all zeros will represent - // an inactive light). - } - } - else - { - // The more interesting case is when we have a `LightArray<L,N>`, - // in which case we need to encode the first field (the light count)... - // - lightTypeEncoder.writeField<int32_t>(0, int32_t(lightTypeArray->lights.size())); - // - // ... followed by an array of values of type `L` in the second field. - // We will only write to the first `lightCount` entries, which may be - // less than `N`. We will rely on dynamic looping in the shader to - // not access the entries past that point. - // - ParameterBlockEncoder arrayEncoder = lightTypeEncoder.beginField(1); - for (size_t ii = 0; ii < lightCount; ++ii) - { - lightTypeArray->lights[ii]->fillInParameterBlock(arrayEncoder.beginArrayElement(ii)); - } - } - } - } -}; - -// Now that we've written all the required infrastructure code for -// the application's renderer and shader library, we can move on -// to the main logic. -// -// We will again structure our example application as a C++ `struct`, -// so that we can scope its allocations for easy cleanup, rather than -// use global variables. -// -struct ModelViewer { - -Window* gWindow; -Slang::ComPtr<gfx::IRenderer> gRenderer; -ComPtr<gfx::ISwapchain> gSwapchain; -ComPtr<IFramebufferLayout> gFramebufferLayout; -Slang::List<ComPtr<gfx::IFramebuffer>> gFramebuffers; - -// We keep a pointer to the one effect we are using (for a forward -// rendering pass), plus the parameter-block layouts for our `PerView` -// and `PerModel` shader types. -// -RefPtr<Effect> gEffect; -RefPtr<ParameterBlockLayout> gPerViewParameterBlockLayout; -RefPtr<ParameterBlockLayout> gPerModelParameterBlockLayout; - -RefPtr<ShaderCache> shaderCache; -RefPtr<GUI> gui; - -// Most of the application state is stored in the list of loaded models, -// as well as the active light source (a single light for now). -// -std::vector<RefPtr<Model>> gModels; -RefPtr<LightEnv> lightEnv; - - -// During startup the application will load one or more models and -// add them to the `gModels` list. -// -void loadAndAddModel( - char const* inputPath, - ModelLoader::LoadFlags loadFlags = 0, - float scale = 1.0f) -{ - auto model = loadModel(gRenderer, inputPath, loadFlags, scale); - if(!model) return; - gModels.push_back(model); -} - -int gWindowWidth = 1024; -int gWindowHeight = 768; -const uint32_t kSwapchainImageCount = 2; - -// Our "simulation" state consists of just a few values. -// -uint64_t lastTime = 0; - -//glm::vec3 lightDir = normalize(glm::vec3(10, 10, 10)); -//glm::vec3 lightColor = glm::vec3(1, 1, 1); - -glm::vec3 cameraPosition = glm::vec3(1.75, 1.25, 5); -glm::quat cameraOrientation = glm::quat(1, glm::vec3(0)); - -float translationScale = 0.5f; -float rotationScale = 0.025f; - - -// In order to control camera movement, we will -// use good old WASD -bool wPressed = false; -bool aPressed = false; -bool sPressed = false; -bool dPressed = false; - -bool isMouseDown = false; -float lastMouseX; -float lastMouseY; - -void handleEvent(Event const& event) -{ - switch( event.code ) - { - case EventCode::KeyDown: - case EventCode::KeyUp: - { - bool isDown = event.code == EventCode::KeyDown; - switch(event.u.key) - { - default: - break; - - case KeyCode::W: wPressed = isDown; break; - case KeyCode::A: aPressed = isDown; break; - case KeyCode::S: sPressed = isDown; break; - case KeyCode::D: dPressed = isDown; break; - } - } - break; - - case EventCode::MouseDown: - { - isMouseDown = true; - lastMouseX = event.u.mouse.x; - lastMouseY = event.u.mouse.y; - } - break; - - case EventCode::MouseMoved: - { - if( isMouseDown ) - { - float deltaX = event.u.mouse.x - lastMouseX; - float deltaY = event.u.mouse.y - lastMouseY; - - cameraOrientation = glm::rotate(cameraOrientation, -deltaX * rotationScale, glm::vec3(0,1,0)); - cameraOrientation = glm::rotate(cameraOrientation, -deltaY * rotationScale, glm::vec3(1,0,0)); - - cameraOrientation = normalize(cameraOrientation); - - lastMouseX = event.u.mouse.x; - lastMouseY = event.u.mouse.y; - } - } - break; - - case EventCode::MouseUp: - isMouseDown = false; - break; - - default: - break; - } -} - -static void _handleEvent(Event const& event) -{ - ModelViewer* app = (ModelViewer*) getUserData(event.window); - app->handleEvent(event); -} - -// The overall initialization logic is quite similar to -// the earlier example. The biggest difference is that we -// create instances of our application-specific parameter -// block layout and effect types instead of just creating -// raw graphics API objects. -// -Result initialize() -{ - WindowDesc windowDesc; - windowDesc.title = "Model Viewer"; - windowDesc.width = gWindowWidth; - windowDesc.height = gWindowHeight; - windowDesc.eventHandler = &_handleEvent; - windowDesc.userData = this; - gWindow = createWindow(windowDesc); - - IRenderer::Desc rendererDesc = {}; - rendererDesc.rendererType = gfx::RendererType::DirectX11; - gfxCreateRenderer(&rendererDesc, gRenderer.writeRef()); - - InputElementDesc inputElements[] = { - {"POSITION", 0, Format::RGB_Float32, offsetof(Model::Vertex, position) }, - {"NORMAL", 0, Format::RGB_Float32, offsetof(Model::Vertex, normal) }, - {"UV", 0, Format::RG_Float32, offsetof(Model::Vertex, uv) }, - }; - auto inputLayout = gRenderer->createInputLayout( - &inputElements[0], - 3); - if(!inputLayout) return SLANG_FAIL; - - // Create swapchain and framebuffers. - gfx::ISwapchain::Desc swapchainDesc = {}; - swapchainDesc.format = gfx::Format::RGBA_Unorm_UInt8; - swapchainDesc.width = gWindowWidth; - swapchainDesc.height = gWindowHeight; - swapchainDesc.imageCount = kSwapchainImageCount; - gSwapchain = gRenderer->createSwapchain( - swapchainDesc, gfx::WindowHandle::FromHwnd(getPlatformWindowHandle(gWindow))); - - IFramebufferLayout::AttachmentLayout renderTargetLayout = {gSwapchain->getDesc().format, 1}; - IFramebufferLayout::AttachmentLayout depthLayout = {gfx::Format::D_Float32, 1}; - IFramebufferLayout::Desc framebufferLayoutDesc; - framebufferLayoutDesc.renderTargetCount = 1; - framebufferLayoutDesc.renderTargets = &renderTargetLayout; - framebufferLayoutDesc.depthStencil = &depthLayout; - SLANG_RETURN_ON_FAIL( - gRenderer->createFramebufferLayout(framebufferLayoutDesc, gFramebufferLayout.writeRef())); - - for (uint32_t i = 0; i < kSwapchainImageCount; i++) - { - gfx::ITextureResource::Desc depthBufferDesc; - depthBufferDesc.setDefaults(gfx::IResource::Usage::DepthWrite); - depthBufferDesc.init2D( - gfx::IResource::Type::Texture2D, - gfx::Format::D_Float32, - gSwapchain->getDesc().width, - gSwapchain->getDesc().height, - 0); - - ComPtr<gfx::ITextureResource> depthBufferResource = gRenderer->createTextureResource( - gfx::IResource::Usage::DepthWrite, depthBufferDesc, nullptr); - ComPtr<gfx::ITextureResource> colorBuffer; - gSwapchain->getImage(i, colorBuffer.writeRef()); - - gfx::IResourceView::Desc colorBufferViewDesc; - memset(&colorBufferViewDesc, 0, sizeof(colorBufferViewDesc)); - colorBufferViewDesc.format = gSwapchain->getDesc().format; - colorBufferViewDesc.renderTarget.shape = gfx::IResource::Type::Texture2D; - colorBufferViewDesc.type = gfx::IResourceView::Type::RenderTarget; - ComPtr<gfx::IResourceView> rtv = - gRenderer->createTextureView(colorBuffer.get(), colorBufferViewDesc); - - gfx::IResourceView::Desc depthBufferViewDesc; - memset(&depthBufferViewDesc, 0, sizeof(depthBufferViewDesc)); - depthBufferViewDesc.format = gfx::Format::D_Float32; - depthBufferViewDesc.renderTarget.shape = gfx::IResource::Type::Texture2D; - depthBufferViewDesc.type = gfx::IResourceView::Type::DepthStencil; - ComPtr<gfx::IResourceView> dsv = - gRenderer->createTextureView(depthBufferResource.get(), depthBufferViewDesc); - - gfx::IFramebuffer::Desc framebufferDesc; - framebufferDesc.renderTargetCount = 1; - framebufferDesc.depthStencilView = dsv.get(); - framebufferDesc.renderTargetViews = rtv.readRef(); - framebufferDesc.layout = gFramebufferLayout; - ComPtr<gfx::IFramebuffer> frameBuffer = gRenderer->createFramebuffer(framebufferDesc); - gFramebuffers.add(frameBuffer); - } - - // Unlike the earlier example, we will not generate final shader kernel - // code during initialization. Instead, we simply load the shader module - // so that we can perform reflection and allocate resources. - // - auto shaderModule = loadShaderModule(gRenderer, "shaders.slang"); - if(!shaderModule) return SLANG_FAIL; - - // Once the shader code has been loaded, we can look up types declared - // in the shader code by name and perform reflection on them to determine - // parameter block layouts, etc. - // - // A more advanced application might load this information on-demand - // and potentially tie into an application-level reflection system - // that already knows the string names of its types (e.g., to connect - // the `PerView` type in shader code to the `PerView` type declared - // in the application code). - // - gPerViewParameterBlockLayout = getParameterBlockLayout( - shaderModule, "PerView"); - gPerModelParameterBlockLayout = getParameterBlockLayout( - shaderModule, "PerModel"); - // - // Note how we are able to load the type definition for `SimpleMaterial` - // from the Slang shader module even though the `SimpleMaterial` type - // is not actually *used* by any entry point in the file. - // - SimpleMaterial::gParameterBlockLayout = getParameterBlockLayout( - shaderModule, "SimpleMaterial"); - - // We also load a shader program based on vertex/fragment shaders in our - // module, and then use this to create an application-level effect. - // - // Note that the `loadProgram` operation here does *not* invoke any - // Slang compilation, because the shader module was already completely - // parsed, checked, etc. by the logic in `loadShaderModule()` above. - // - auto program = loadProgram(shaderModule, "vertexMain", "fragmentMain"); - if(!program) return SLANG_FAIL; - - RefPtr<Effect> effect = new Effect(); - effect->program = program; - effect->inputLayout = inputLayout; - effect->renderTargetCount = 1; - gEffect = effect; - - // In order to create specialized variants of the effect(s) that - // get used for rendering, we will use a shader cache. - // - shaderCache = new ShaderCache(); - - // We will create a lighting environment layout that can hold a few point - // and directional lights, and then initialize a lighting environment - // with just a single point light. - // - RefPtr<LightEnvLayout> lightEnvLayout = new LightEnvLayout(shaderModule); - lightEnvLayout->addLightType<PointLight>(10); - lightEnvLayout->addLightType<DirectionalLight>(2); - - lightEnv = new LightEnv(lightEnvLayout); - lightEnv->add(new PointLight()); - - // Once we have created all our graphcis API and application resources, - // we can start to load models. For now we are keeping things extremely - // simple by using a trivial `.obj` file that can be checked into source - // control. - // - // Support for loading more interesting/complex models will be added - // to this example over time (although model loading is *not* the focus). - // - loadAndAddModel("cube.obj"); - - // We will do some GUI rendering in this app, using "Dear, IMGUI", - // so we need to do the appropriate initialization work here. - gui = new GUI(gWindow, gRenderer, gFramebufferLayout); - - showWindow(gWindow); - - return SLANG_OK; -} - -// With the setup work done, we can look at the per-frame rendering -// logic to see how the application will drive the `RenderContext` -// type to perform both shader parameter binding and code specialization. -// -void renderFrame() -{ - gui->beginFrame(); - - // In order to see that things are rendering properly we need some - // kind of animation, so we will compute a crude delta-time value here. - // - if(!lastTime) lastTime = getCurrentTime(); - uint64_t currentTime = getCurrentTime(); - float deltaTime = float(double(currentTime - lastTime) / double(getTimerFrequency())); - lastTime = currentTime; - - // We will use the GLM library to do the matrix math required - // to set up our various transformation matrices. - // - glm::mat4x4 identity = glm::mat4x4(1.0f); - glm::mat4x4 projection = glm::perspective( - glm::radians(60.0f), - float(gWindowWidth) / float(gWindowHeight), - 0.1f, - 1000.0f); - - // We are implementing a *very* basic 6DOF first-person - // camera movement model. - // - glm::mat3x3 cameraOrientationMat(cameraOrientation); - glm::vec3 forward = -cameraOrientationMat[2]; - glm::vec3 right = cameraOrientationMat[0]; - - glm::vec3 movement = glm::vec3(0); - if(wPressed) movement += forward; - if(sPressed) movement -= forward; - if(aPressed) movement -= right; - if(dPressed) movement += right; - - cameraPosition += deltaTime * translationScale * movement; - - glm::mat4x4 view = identity; - view *= glm::mat4x4(inverse(cameraOrientation)); - view = glm::translate(view, -cameraPosition); - - glm::mat4x4 viewProjection = projection * view; - - // Some of the basic rendering setup is identical to the previous example. - // - auto frameIndex = gSwapchain->acquireNextImage(); - gRenderer->setFramebuffer(gFramebuffers[frameIndex]); - - gfx::Viewport viewport = {}; - viewport.maxZ = 1.0f; - viewport.extentX = (float)gWindowWidth; - viewport.extentY = (float)gWindowHeight; - gRenderer->setViewportAndScissor(viewport); - - static const float kClearColor[] = { 0.25, 0.25, 0.25, 1.0 }; - gRenderer->setClearColor(kClearColor); - gRenderer->clearFrame(); - gRenderer->setPrimitiveTopology(PrimitiveTopology::TriangleList); - - // Now we will start in on the more interesting rendering logic, - // by creating the `RenderContext` we will use for submission. - // - // Note: in a multi-threaded submission case, the application would - // need to use a distinct `RenderContext` on each thread. - // - RenderContext context(gRenderer, shaderCache); - - // Next we set the effect that we will use for our forward rendering - // pass. Note that an example with multiple passes would use a - // distinct effect for each pass. - // - context.setEffect(gEffect); - - // We are only rendering one view, so we can fill in a per-view - // parameter block once and use it across all draw calls. - // This parameter block will be different every frame, so we - // allocate a transient parameter block rather than try to - // carefully track and re-use an allocation. - // - auto viewParameterBlock = allocateTransientParameterBlock( - gPerViewParameterBlockLayout); - { - auto encoder = viewParameterBlock->beginEncoding(); - encoder.writeField(0, viewProjection); - encoder.writeField(1, cameraPosition); - encoder.finishEncoding(); - } - // - // Note: the assignment of indices to parameter blocks is driven - // by their order of declaration in the shader code, so we know - // that the per-view parameter block has index zero. Alternatively, - // an application could use reflection API operations to look up - // the index of a parameter block based on its name. - // - context.setParameterBlock(0, viewParameterBlock); - - // Our `LightEnv` type knows how to turn itself into a parameter - // block, so we just create and bind it here. - // - auto lightEnvParameterBlock = lightEnv->createParameterBlock(); - context.setParameterBlock(2, lightEnvParameterBlock); - - // The majority of our rendering logic is handled as a loop - // over the models in the scene, and their meshes. - // - for(auto& model : gModels) - { - gRenderer->setVertexBuffer(0, model->vertexBuffer, sizeof(Model::Vertex)); - gRenderer->setIndexBuffer(model->indexBuffer, Format::R_UInt32); - - // For each model we provide a parameter - // block that holds the per-model transformation - // parameters, corresponding to the `PerModel` type - // in the shader code. - // - // Like the view parameter block, it makes sense - // to allocate this block as a transient allocation, - // since its contents would be different on the next - // frame anyway. - // - glm::mat4x4 modelTransform = identity; - glm::mat4x4 inverseTransposeModelTransform = inverse(transpose(modelTransform)); - - auto modelParameterBlock = allocateTransientParameterBlock( - gPerModelParameterBlockLayout); - { - auto encoder = modelParameterBlock->beginEncoding(); - encoder.writeField(0, modelTransform); - encoder.writeField(1, inverseTransposeModelTransform); - encoder.finishEncoding(); - } - context.setParameterBlock(1, modelParameterBlock); - - // Now we loop over the meshes in the model. - // - // A more advanced rendering loop would sort things by material - // rather than by model, to avoid overly frequent state changes. - // We are just doing something simple for the purposes of an - // exmple program. - // - for(auto& mesh : model->meshes) - { - // Each mesh has a material, and each material has its own - // parameter block that was created at load time, so we - // can just re-use the persistent parameter block for the - // chosen material. - // - // Note that binding the material parameter block here is - // both selecting the values to use for various material - // parameters as well as the *code* to use for material - // evaluation (based on the concrete shader type that - // is implementing the `IMaterial` interface). - // - context.setParameterBlock( - 3, - mesh->material->parameterBlock); - - // Once we've set up all the parameter blocks needed - // for a given drawing operation, we need to flush - // any pending state changes (e.g., if the type of - // material changed, a shader switch might be - // required). - // - context.flushState(gFramebufferLayout); - - gRenderer->drawIndexed(mesh->indexCount, mesh->firstIndex); - } - } - - ImGui::Begin("Slang Model Viewer Example"); - ImGui::Text("Average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - if (ImGui::Button("Reload Shaders")) - { - shaderCache->clear(); - } - if( ImGui::CollapsingHeader("Lights") ) - { - lightEnv->doUI(); - } - if (ImGui::CollapsingHeader("Camera")) - { - ImGui::InputFloat3("position", &cameraPosition[0]); - ImGui::InputFloat3("orientation[0]", &cameraOrientationMat[0][0]); - ImGui::InputFloat3("orientation[1]", &cameraOrientationMat[1][0]); - ImGui::InputFloat3("orientation[2]", &cameraOrientationMat[2][0]); - } - - ImGui::End(); - - gSwapchain->present(); - -} - -void finalize() -{ - // Because we've stored a reference to some graphics API objects - // in a class-static variable (effectively a global) we need - // to clear those out before tearing down the application so - // that we aren't relying on C++ global destructors to tear - // down our application cleanly. - // - gRenderer->waitForGpu(); - SimpleMaterial::gParameterBlockLayout = nullptr; - destroyWindow(gWindow); -} - -}; - -void innerMain(ApplicationContext* context) -{ - ModelViewer app; - if(SLANG_FAILED(app.initialize())) - { - exitApplication(context, 1); - } - - while(dispatchEvents(context)) - { - app.renderFrame(); - } - - app.finalize(); -} -GFX_UI_MAIN(innerMain) - -#endif diff --git a/examples/experimental/model-viewer/README.md b/examples/model-viewer/README.md index a350a48a2..a350a48a2 100644 --- a/examples/experimental/model-viewer/README.md +++ b/examples/model-viewer/README.md diff --git a/examples/experimental/model-viewer/cube.mtl b/examples/model-viewer/cube.mtl index 6c8eeb10b..6c8eeb10b 100644 --- a/examples/experimental/model-viewer/cube.mtl +++ b/examples/model-viewer/cube.mtl diff --git a/examples/model-viewer/cube.obj b/examples/model-viewer/cube.obj new file mode 100644 index 000000000..9213e178b --- /dev/null +++ b/examples/model-viewer/cube.obj @@ -0,0 +1,31 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/examples/model-viewer/main.cpp b/examples/model-viewer/main.cpp new file mode 100644 index 000000000..3d7a9fe34 --- /dev/null +++ b/examples/model-viewer/main.cpp @@ -0,0 +1,922 @@ +// This example is out of date and currently disabled from build. +// The `gfx` layer has been refactored with a new shader-object model +// that will greatly simplify shader binding and specialization. +// This example should be updated to use the shader-object API in `gfx`. + +// main.cpp + +// +// This example is much more involved than the `hello-world` example, +// so readers are encouraged to work through the simpler code first +// before diving into this application. We will gloss over parts of +// the code that are similar to the code in `hello-world`, and +// instead focus on the new code that is required to use Slang in +// more advanced ways. +// + +// We still need to include the Slang header to use the Slang API +// +#include <slang.h> +#include "slang-com-helper.h" + +// We will again make use of a graphics API abstraction +// layer that implements the shader-object idiom based on Slang's +// `ParameterBlock` and `interface` features to simplify shader specialization +// and parameter binding. +// +#include "slang-gfx.h" +#include "tools/gfx-util/shader-cursor.h" +#include "tools/platform/model.h" +#include "tools/platform/vector-math.h" +#include "tools/platform/window.h" +#include "tools/platform/gui.h" +#include "examples/example-base/example-base.h" + +#include <map> +#include <sstream> + +using namespace gfx; +using Slang::RefObject; +using Slang::RefPtr; + +struct RendererContext +{ + IDevice* device; + slang::IModule* shaderModule; + slang::ShaderReflection* slangReflection; + ComPtr<IShaderProgram> shaderProgram; + + slang::TypeReflection* perViewShaderType; + slang::TypeReflection* perModelShaderType; + + Result init(IDevice* inDevice) + { + device = inDevice; + ComPtr<ISlangBlob> diagnostic; + shaderModule = device->getSlangSession()->loadModule("shaders", diagnostic.writeRef()); + diagnoseIfNeeded(diagnostic); + + // Compose the shader program for drawing models by combining the shader module + // and entry points ("vertexMain" and "fragmentMain"). + char const* vertexEntryPointName = "vertexMain"; + ComPtr<slang::IEntryPoint> vertexEntryPoint; + SLANG_RETURN_ON_FAIL( + shaderModule->findEntryPointByName(vertexEntryPointName, vertexEntryPoint.writeRef())); + + char const* fragEntryPointName = "fragmentMain"; + ComPtr<slang::IEntryPoint> fragEntryPoint; + SLANG_RETURN_ON_FAIL( + shaderModule->findEntryPointByName(fragEntryPointName, fragEntryPoint.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(shaderModule); + componentTypes.add(vertexEntryPoint); + componentTypes.add(fragEntryPoint); + + // 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> composedProgram; + ComPtr<ISlangBlob> diagnosticsBlob; + SlangResult result = device->getSlangSession()->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + composedProgram.writeRef(), + diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + SLANG_RETURN_ON_FAIL(result); + slangReflection = composedProgram->getLayout(); + + // At this point, `composedProgram` represents the shader program + // we want to run, and the compute shader there have been checked. + // We can create a `gfx::IShaderProgram` object from `composedProgram` + // so it may be used by the graphics layer. + gfx::IShaderProgram::Desc programDesc = {}; + programDesc.pipelineType = gfx::PipelineType::Graphics; + programDesc.slangProgram = composedProgram.get(); + + shaderProgram = device->createProgram(programDesc); + + // Get other shader types that we will use for creating shader objects. + perViewShaderType = slangReflection->findTypeByName("PerView"); + perModelShaderType = slangReflection->findTypeByName("PerModel"); + + return SLANG_OK; + } +}; + +// Our application code has a rudimentary material system, +// to match the `IMaterial` abstraction used in the shade code. +// +struct Material : RefObject +{ + // The key feature of a matrial in our application is that + // it can provide a shader object that describes it and + // its parameters. The contents of the shader object will + // be any colors, textures, etc. that the material needs, + // while the Slang type that was used to allocate the + // block will be an implementation of `IMaterial` that + // provides the evaluation logic for the material. + + // Each subclass of `Material` will provide a routine to + // create a shader object that stores its shader parameters. + virtual IShaderObject* createShaderObject(RendererContext* context) = 0; + + // The shader object for a material will be stashed here + // after it is created. + ComPtr<IShaderObject> shaderObject; +}; + +// For now we have only a single implementation of `Material`, +// which corresponds to the `SimpleMaterial` type in our shader +// code. +// +struct SimpleMaterial : Material +{ + glm::vec3 diffuseColor; + glm::vec3 specularColor; + float specularity; + + // Create a shader object that contains the type info and parameter values + // that represent an instance of `SimpleMaterial`. + IShaderObject* createShaderObject(RendererContext* context) override + { + auto program = context->slangReflection; + auto shaderType = program->findTypeByName("SimpleMaterial"); + shaderObject = context->device->createShaderObject(shaderType); + gfx::ShaderCursor cursor(shaderObject); + cursor["diffuseColor"].setData(&diffuseColor, sizeof(diffuseColor)); + cursor["specularColor"].setData(&specularColor, sizeof(specularColor)); + cursor["specularity"].setData(&specularity, sizeof(specularity)); + return shaderObject.get(); + } +}; + +// With the `Material` abstraction defined, we can go on to define +// the representation for loaded models that we will use. +// +// A `Model` will own vertex/index buffers, along with a list of meshes, +// while each `Mesh` will own a material and a range of indices. +// For this example we will be loading models from `.obj` files, but +// that is just a simple lowest-common-denominator choice. +// +struct Mesh : RefObject +{ + RefPtr<Material> material; + int firstIndex; + int indexCount; +}; +struct Model : RefObject +{ + typedef platform::ModelLoader::Vertex Vertex; + + ComPtr<IBufferResource> vertexBuffer; + ComPtr<IBufferResource> indexBuffer; + PrimitiveTopology primitiveTopology; + int vertexCount; + int indexCount; + std::vector<RefPtr<Mesh>> meshes; +}; +// +// Loading a model from disk is done with the help of some utility +// code for parsing the `.obj` file format, so that the application +// mostly just registers some callbacks to allocate the objects +// used for its representation. +// +RefPtr<Model> loadModel( + RendererContext* context, + char const* inputPath, + platform::ModelLoader::LoadFlags loadFlags = 0, + float scale = 1.0f) +{ + // The model loading interface using a C++ interface of + // callback functions to handle creating the application-specific + // representation of meshes, materials, etc. + // + struct Callbacks : platform::ModelLoader::ICallbacks + { + RendererContext* context; + void* createMaterial(MaterialData const& data) override + { + SimpleMaterial* material = new SimpleMaterial(); + material->diffuseColor = data.diffuseColor; + material->specularColor = data.specularColor; + material->specularity = data.specularity; + material->createShaderObject(context); + return material; + } + + void* createMesh(MeshData const& data) override + { + Mesh* mesh = new Mesh(); + mesh->firstIndex = data.firstIndex; + mesh->indexCount = data.indexCount; + mesh->material = (Material*)data.material; + return mesh; + } + + void* createModel(ModelData const& data) override + { + Model* model = new Model(); + model->vertexBuffer = data.vertexBuffer; + model->indexBuffer = data.indexBuffer; + model->primitiveTopology = data.primitiveTopology; + model->vertexCount = data.vertexCount; + model->indexCount = data.indexCount; + + int meshCount = data.meshCount; + for (int ii = 0; ii < meshCount; ++ii) + model->meshes.push_back((Mesh*)data.meshes[ii]); + + return model; + } + }; + Callbacks callbacks; + callbacks.context = context; + + // We instantiate a model loader object and then use it to + // try and load a model from the chosen path. + // + platform::ModelLoader loader; + loader.device = context->device; + loader.loadFlags = loadFlags; + loader.scale = scale; + loader.callbacks = &callbacks; + Model* model = nullptr; + if (SLANG_FAILED(loader.load(inputPath, (void**)&model))) + { + log("failed to load '%s'\n", inputPath); + return nullptr; + } + + return model; +} + +// Along with materials, our application needs to be able to represent +// multiple light sources in the scene. For this task we will use a C++ +// inheritance hierarchy rooted at `Light` to match the `ILight` +// interface in Slang. + +struct Light : RefObject +{ + // A light must be able to create a shader object defining its + // corresponding shader type and parameter values. + virtual IShaderObject* createShaderObject(RendererContext* context) = 0; + + // Retrieves the shader type for this light object. + virtual slang::TypeReflection* getShaderType(RendererContext* context) = 0; + + // The shader object for a light will be stashed here + // after it is created. + ComPtr<IShaderObject> shaderObject; +}; + +// Helper function to retrieve the underlying shader type of `T`. +template<typename T> +slang::TypeReflection* getShaderType(RendererContext* context) +{ + auto program = context->slangReflection; + auto shaderType = program->findTypeByName(T::getTypeName()); + return shaderType; +} + +// We will provide two nearly trivial implementations of `Light` for now, +// to show the kind of application code needed to line up with the corresponding +// types defined in the Slang shader code for this application. + +struct DirectionalLight : Light +{ + glm::vec3 direction = normalize(glm::vec3(1)); + glm::vec3 intensity = glm::vec3(1); + + static const char* getTypeName() { return "DirectionalLight"; } + + virtual IShaderObject* createShaderObject(RendererContext* context) override + { + auto shaderType = ::getShaderType<DirectionalLight>(context); + shaderObject = context->device->createShaderObject(shaderType); + gfx::ShaderCursor cursor(shaderObject); + cursor["direction"].setData(&direction, sizeof(direction)); + cursor["intensity"].setData(&intensity, sizeof(intensity)); + return shaderObject.get(); + } + + virtual slang::TypeReflection* getShaderType(RendererContext* context) override + { + return ::getShaderType<DirectionalLight>(context); + } +}; + +struct PointLight : Light +{ + glm::vec3 position = glm::vec3(0); + glm::vec3 intensity = glm::vec3(1); + + static const char* getTypeName() { return "PointLight"; } + + virtual IShaderObject* createShaderObject(RendererContext* context) override + { + auto shaderType = ::getShaderType<PointLight>(context); + shaderObject = context->device->createShaderObject(shaderType); + gfx::ShaderCursor cursor(shaderObject); + cursor["position"].setData(&position, sizeof(position)); + cursor["intensity"].setData(&intensity, sizeof(intensity)); + return shaderObject.get(); + } + + virtual slang::TypeReflection* getShaderType(RendererContext* context) override + { + return ::getShaderType<PointLight>(context); + } +}; + +// Rendering is usually done with collections of lights rather than single +// lights. This application will use a concept of "light environments" to +// group together lights for rendering. +// +// We want to be *able* to specialize our shader code based on the particular +// types of lights in a scene, but we also do not want to over-specialize +// and, e.g., use differnt specialized shaders for a scene with 99 point +// lights vs. 100. +// +// This particular application will use a notion of a "layout" for a lighting +// environment, which specifies the allowed types of lights, and the maximum +// number of lights of each type. Different lighting environment layouts +// will yield different specialized code. + +struct LightEnvLayout : public RefObject +{ + // Our lighting environment layout will track layout + // information for several different arrays: one + // for each supported light type. + // + struct LightArrayLayout : RefObject + { + Int maximumCount = 0; + std::string typeName; + }; + std::vector<LightArrayLayout> lightArrayLayouts; + std::map<slang::TypeReflection*, Int> mapLightTypeToArrayIndex; + slang::TypeReflection* shaderType = nullptr; + + void addLightType(RendererContext* context, slang::TypeReflection* lightType, Int maximumCount) + { + Int arrayIndex = (Int)lightArrayLayouts.size(); + LightArrayLayout layout; + layout.maximumCount = maximumCount; + + // When the user adds a light type `X` to a light-env layout, + // we need to compute the corresponding Slang type and + // layout information to use. If only a single light is + // supported, this will just be the type `X`, while for + // any other count this will be a `LightArray<X, maximumCount>` + // + if (maximumCount <= 1) + { + layout.typeName = lightType->getName(); + } + else + { + auto program = context->slangReflection; + std::stringstream typeNameBuilder; + typeNameBuilder << "LightArray<" << lightType->getName() << "," << maximumCount + << ">"; + layout.typeName = typeNameBuilder.str(); + } + + lightArrayLayouts.push_back(layout); + mapLightTypeToArrayIndex.insert(std::make_pair(lightType, arrayIndex)); + } + + template<typename T> void addLightType(RendererContext* context, Int maximumCount) + { + addLightType(context, getShaderType<T>(context), maximumCount); + } + + Int getArrayIndexForType(slang::TypeReflection* lightType) + { + auto iter = mapLightTypeToArrayIndex.find(lightType); + if (iter != mapLightTypeToArrayIndex.end()) + return iter->second; + + return -1; + } +}; + +// A `LightEnv` follows the structure of a `LightEnvLayout`, +// and provides storage for zero or more lights of various +// different types (up to the limits imposed by the layout). +// +struct LightEnv : public RefObject +{ + // A light environment is always created from a fixed layout + // in this application, so the constructor allocates an array + // for the per-light-type data. + // + // A more complex example might dynamically determine the + // layout based on the number of lights of each type active + // in the scene, with some quantization applied to avoid + // generating too many shader specializations. + // + // Note: the kind of specialization going on here would also + // be applicable to a deferred or "forward+" renderer, insofar + // as it sets the bounds on the total set of lights for + // a scene/frame, while per-tile/-cluster light lists would + // probably just be indices into the global structure. + // + RefPtr<LightEnvLayout> layout; + RendererContext* context; + LightEnv(RefPtr<LightEnvLayout> layout, RendererContext* inContext) + : layout(layout) + , context(inContext) + { + for (auto arrayLayout : layout->lightArrayLayouts) + { + RefPtr<LightArray> lightArray = new LightArray(); + lightArray->layout = arrayLayout; + lightArrays.push_back(lightArray); + } + } + + // For each light type, we track the layout information, + // plus the list of active lights of that type. + // + struct LightArray : RefObject + { + LightEnvLayout::LightArrayLayout layout; + std::vector<RefPtr<Light>> lights; + }; + std::vector<RefPtr<LightArray>> lightArrays; + + RefPtr<LightArray> getArrayForType(slang::TypeReflection* type) + { + auto index = layout->getArrayIndexForType(type); + return lightArrays[index]; + } + + void add(RefPtr<Light> light) + { + auto array = getArrayForType(light->getShaderType(context)); + array->lights.push_back(light); + } + + // Get the proper shader type that represents this lighting environment. + slang::TypeReflection* getShaderType() + { + // Given a lighting environment with N light types: + // + // L0, L1, ... LN + // + // We want to compute the Slang type: + // + // LightPair<L0, LightPair<L1, ... LightPair<LN-1, LN>>> + // + // This is most easily accomplished by doing a "fold" while + // walking the array in reverse order. + + std::string currentEnvTypeName; + auto arrayCount = layout->lightArrayLayouts.size(); + for (size_t ii = arrayCount; ii--;) + { + auto arrayInfo = layout->lightArrayLayouts[ii]; + + if (!currentEnvTypeName.size()) + { + // The is the right-most entry, so it is the base case for our "fold". + currentEnvTypeName = arrayInfo.typeName; + } + else + { + // Fold one entry: `envLayout = LightPair<a, envLayout>` + std::stringstream typeBuilder; + typeBuilder << "LightPair<" << arrayInfo.typeName << "," << currentEnvTypeName + << ">"; + currentEnvTypeName = typeBuilder.str(); + } + } + + if (!currentEnvTypeName.size()) + { + // Handle the special case of *zero* light types. + currentEnvTypeName = "EmptyLightEnv"; + } + return context->slangReflection->findTypeByName(currentEnvTypeName.c_str()); + } + + // Because the lighting environment will often change between frames, + // we will not try to optimize for the case where it doesn't change, + // and will instead create a "transient" shader object from + // scratch every frame. + // + ComPtr<IShaderObject> createShaderObject() + { + auto specializedType = getShaderType(); + + auto shaderObject = context->device->createShaderObject(specializedType); + ShaderCursor cursor(shaderObject); + // When filling in the shader object for a lighting + // environment, we mostly follow the structure of + // the type that was computed by the `LightEnv::getShaderType`: + // + // LightPair<A, LightPair<B, ... LightPair<Y, Z>>> + // + // we will keep `encoder` pointed at the "spine" of this + // structure (so at an element that represents a `LightPair`, + // except for the special case of the last item like `Z` above). + // + // For each light type, we will then encode the data as + // needed for the light type (`A` then `B` then ...) + // + size_t lightTypeCount = lightArrays.size(); + for (size_t tt = 0; tt < lightTypeCount; ++tt) + { + // The encoder for the very last item will + // just be the one on the "spine" of the list. + auto lightTypeCursor = cursor; + if (tt != lightTypeCount - 1) + { + // In the common case `encoder` is set up + // for writing to a `LightPair<X, Y>` so + // we ant to set up the `lightTypeEncoder` + // for writing to an `X` (which is the first + // field of `LightPair`, and then have + // `encoder` move on to the `Y` (the rest + // of the list of light types). + // + lightTypeCursor = cursor["first"]; + cursor = cursor["second"]; + } + + auto& lightTypeArray = lightArrays[tt]; + size_t lightCount = lightTypeArray->lights.size(); + size_t maxLightCount = lightTypeArray->layout.maximumCount; + + // Recall that we are representing the data for a single + // light type `L` as either an instance of type `L` (if + // only a single light is supported), or as an instance + // of the type `LightArray<L,N>`. + // + if (maxLightCount == 1) + { + // This is the case where the maximu number of lights of + // the given type was set as one, so we just have a value + // of type `L`, and can tell the first light in our application-side + // array to encode itself into that location. + + if (lightCount > 0) + { + lightTypeCursor.setObject( + lightTypeArray->lights[0]->createShaderObject(context)); + } + else + { + // We really ought to zero out the entry in this case + // (under the assumption that all zeros will represent + // an inactive light). + } + } + else + { + // The more interesting case is when we have a `LightArray<L,N>`, + // in which case we need to fill in the first field (the light count)... + // + uint32_t lightCount = uint32_t(lightTypeArray->lights.size()); + lightTypeCursor["count"].setData(&lightCount, sizeof(lightCount)); + // + // ... followed by an array of values of type `L` in the second field. + // We will only write to the first `lightCount` entries, which may be + // less than `N`. We will rely on dynamic looping in the shader to + // not access the entries past that point. + // + auto arrayCursor = lightTypeCursor["lights"]; + for (size_t ii = 0; ii < lightCount; ++ii) + { + arrayCursor[ii].setObject( + lightTypeArray->lights[ii]->createShaderObject(context)); + } + } + } + return shaderObject; + } +}; + +// Now that we've written all the required infrastructure code for +// the application's renderer and shader library, we can move on +// to the main logic. +// +// We will again structure our example application as a C++ `struct`, +// so that we can scope its allocations for easy cleanup, rather than +// use global variables. +// +struct ModelViewer : WindowedAppBase +{ + +RendererContext context; + +// Most of the application state is stored in the list of loaded models, +// as well as the active light source (a single light for now). +// +std::vector<RefPtr<Model>> gModels; +RefPtr<LightEnv> lightEnv; + +// The pipeline state object we will use to draw models. +ComPtr<IPipelineState> gPipelineState; + +// During startup the application will load one or more models and +// add them to the `gModels` list. +// +void loadAndAddModel( + char const* inputPath, + platform::ModelLoader::LoadFlags loadFlags = 0, + float scale = 1.0f) +{ + auto model = loadModel(&context, inputPath, loadFlags, scale); + if(!model) return; + gModels.push_back(model); +} + +// Our "simulation" state consists of just a few values. +// +uint64_t lastTime = 0; + +//glm::vec3 lightDir = normalize(glm::vec3(10, 10, 10)); +//glm::vec3 lightColor = glm::vec3(1, 1, 1); + +glm::vec3 cameraPosition = glm::vec3(1.75, 1.25, 5); +glm::quat cameraOrientation = glm::quat(1, glm::vec3(0)); + +float translationScale = 0.5f; +float rotationScale = 0.025f; + +// In order to control camera movement, we will +// use good old WASD +bool wPressed = false; +bool aPressed = false; +bool sPressed = false; +bool dPressed = false; + +bool isMouseDown = false; +float lastMouseX = 0.0f; +float lastMouseY = 0.0f; + +void setKeyState(platform::KeyCode key, bool state) +{ + switch (key) + { + default: + break; + case platform::KeyCode::W: + wPressed = state; + break; + case platform::KeyCode::A: + aPressed = state; + break; + case platform::KeyCode::S: + sPressed = state; + break; + case platform::KeyCode::D: + dPressed = state; + break; + } +} +void onKeyDown(platform::KeyEventArgs args) { setKeyState(args.key, true); } +void onKeyUp(platform::KeyEventArgs args) { setKeyState(args.key, false); } + +void onMouseDown(platform::MouseEventArgs args) +{ + isMouseDown = true; + lastMouseX = (float)args.x; + lastMouseY = (float)args.y; +} + +void onMouseMove(platform::MouseEventArgs args) +{ + if (isMouseDown) + { + float deltaX = args.x - lastMouseX; + float deltaY = args.y - lastMouseY; + + cameraOrientation = + glm::rotate(cameraOrientation, -deltaX * rotationScale, glm::vec3(0, 1, 0)); + cameraOrientation = + glm::rotate(cameraOrientation, -deltaY * rotationScale, glm::vec3(1, 0, 0)); + + cameraOrientation = normalize(cameraOrientation); + + lastMouseX = (float)args.x; + lastMouseY = (float)args.y; + } +} +void onMouseUp(platform::MouseEventArgs args) +{ + isMouseDown = false; +} + +// The overall initialization logic is quite similar to +// the earlier example. The biggest difference is that we +// create instances of our application-specific parameter +// block layout and effect types instead of just creating +// raw graphics API objects. +// +Result initialize() +{ + initializeBase("Model Viewer", 1024, 768); + gWindow->events.mouseMove = [this](const platform::MouseEventArgs& e) { onMouseMove(e); }; + gWindow->events.mouseUp = [this](const platform::MouseEventArgs& e) { onMouseUp(e); }; + gWindow->events.mouseDown = [this](const platform::MouseEventArgs& e) { onMouseDown(e); }; + gWindow->events.keyDown = [this](const platform::KeyEventArgs& e) { onKeyDown(e); }; + gWindow->events.keyUp = [this](const platform::KeyEventArgs& e) { onKeyUp(e); }; + + // Initialize `RendererContext`, which loads the shader module from file. + SLANG_RETURN_ON_FAIL(context.init(gDevice)); + + InputElementDesc inputElements[] = { + {"POSITION", 0, Format::RGB_Float32, offsetof(Model::Vertex, position) }, + {"NORMAL", 0, Format::RGB_Float32, offsetof(Model::Vertex, normal) }, + {"UV", 0, Format::RG_Float32, offsetof(Model::Vertex, uv) }, + }; + auto inputLayout = gDevice->createInputLayout( + &inputElements[0], + 3); + if(!inputLayout) return SLANG_FAIL; + + // Create the pipeline state object for drawing models. + GraphicsPipelineStateDesc pipelineStateDesc = {}; + pipelineStateDesc.program = context.shaderProgram; + pipelineStateDesc.framebufferLayout = gFramebufferLayout; + pipelineStateDesc.inputLayout = inputLayout; + pipelineStateDesc.primitiveType = PrimitiveType::Triangle; + pipelineStateDesc.depthStencil.depthFunc = ComparisonFunc::LessEqual; + pipelineStateDesc.depthStencil.depthTestEnable = true; + gPipelineState = gDevice->createGraphicsPipelineState(pipelineStateDesc); + + // We will create a lighting environment layout that can hold a few point + // and directional lights, and then initialize a lighting environment + // with just a single point light. + // + RefPtr<LightEnvLayout> lightEnvLayout = new LightEnvLayout(); + lightEnvLayout->addLightType<PointLight>(&context, 10); + lightEnvLayout->addLightType<DirectionalLight>(&context, 2); + + lightEnv = new LightEnv(lightEnvLayout, &context); + lightEnv->add(new PointLight()); + + // Once we have created all our graphcis API and application resources, + // we can start to load models. For now we are keeping things extremely + // simple by using a trivial `.obj` file that can be checked into source + // control. + // + // Support for loading more interesting/complex models will be added + // to this example over time (although model loading is *not* the focus). + // + loadAndAddModel("cube.obj"); + + return SLANG_OK; +} + +// With the setup work done, we can look at the per-frame rendering +// logic to see how the application will drive the `RenderContext` +// type to perform both shader parameter binding and code specialization. +// +void renderFrame(int frameIndex) override +{ + // In order to see that things are rendering properly we need some + // kind of animation, so we will compute a crude delta-time value here. + // + if(!lastTime) lastTime = getCurrentTime(); + uint64_t currentTime = getCurrentTime(); + float deltaTime = float(double(currentTime - lastTime) / double(getTimerFrequency())); + lastTime = currentTime; + + // We will use the GLM library to do the matrix math required + // to set up our various transformation matrices. + // + glm::mat4x4 identity = glm::mat4x4(1.0f); + auto clientRect = getWindow()->getClientRect(); + glm::mat4x4 projection = glm::perspective( + glm::radians(60.0f), float(clientRect.width) / float(clientRect.height), + 0.1f, + 1000.0f); + + // We are implementing a *very* basic 6DOF first-person + // camera movement model. + // + glm::mat3x3 cameraOrientationMat(cameraOrientation); + glm::vec3 forward = -cameraOrientationMat[2]; + glm::vec3 right = cameraOrientationMat[0]; + + glm::vec3 movement = glm::vec3(0); + if(wPressed) movement += forward; + if(sPressed) movement -= forward; + if(aPressed) movement -= right; + if(dPressed) movement += right; + + cameraPosition += deltaTime * translationScale * movement; + + glm::mat4x4 view = identity; + view *= glm::mat4x4(inverse(cameraOrientation)); + view = glm::translate(view, -cameraPosition); + + glm::mat4x4 viewProjection = projection * view; + + auto drawCommandBuffer = gTransientHeaps[frameIndex]->createCommandBuffer(); + auto drawCommandEncoder = + drawCommandBuffer->encodeRenderCommands(gRenderPass, gFramebuffers[frameIndex]); + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = (float)clientRect.width; + viewport.extentY = (float)clientRect.height; + drawCommandEncoder->setViewportAndScissor(viewport); + drawCommandEncoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + // We are only rendering one view, so we can fill in a per-view + // shader object once and use it across all draw calls. + // + auto viewShaderObject = gDevice->createShaderObject(context.perViewShaderType); + { + ShaderCursor cursor(viewShaderObject); + cursor["viewProjection"].setData(&viewProjection, sizeof(viewProjection)); + cursor["eyePosition"].setData(&cameraPosition, sizeof(cameraPosition)); + } + + // The majority of our rendering logic is handled as a loop + // over the models in the scene, and their meshes. + // + for(auto& model : gModels) + { + auto rootObject = drawCommandEncoder->bindPipeline(gPipelineState); + ShaderCursor rootCursor(rootObject); + rootCursor["gViewParams"].setObject(viewShaderObject); + drawCommandEncoder->setVertexBuffer(0, model->vertexBuffer, sizeof(Model::Vertex)); + drawCommandEncoder->setIndexBuffer(model->indexBuffer, Format::R_UInt32); + + // For each model we provide a parameter + // block that holds the per-model transformation + // parameters, corresponding to the `PerModel` type + // in the shader code. + glm::mat4x4 modelTransform = identity; + glm::mat4x4 inverseTransposeModelTransform = inverse(transpose(modelTransform)); + auto modelShaderObject = gDevice->createShaderObject(context.perModelShaderType); + { + ShaderCursor cursor(modelShaderObject); + cursor["modelTransform"].setData(&modelTransform, sizeof(modelTransform)); + cursor["inverseTransposeModelTransform"].setData( + &inverseTransposeModelTransform, sizeof(inverseTransposeModelTransform)); + } + rootCursor["gModelParams"].setObject(modelShaderObject); + + auto lightShaderObject = lightEnv->createShaderObject(); + rootCursor["gLightEnv"].setObject(lightShaderObject); + + // Now we loop over the meshes in the model. + // + // A more advanced rendering loop would sort things by material + // rather than by model, to avoid overly frequent state changes. + // We are just doing something simple for the purposes of an + // exmple program. + // + for(auto& mesh : model->meshes) + { + // Each mesh has a material, and each material has its own + // parameter block that was created at load time, so we + // can just re-use the persistent parameter block for the + // chosen material. + // + // Note that binding the material parameter block here is + // both selecting the values to use for various material + // parameters as well as the *code* to use for material + // evaluation (based on the concrete shader type that + // is implementing the `IMaterial` interface). + // + rootCursor["gMaterial"].setObject(mesh->material->shaderObject); + + // All the shader parameters and pipeline states have been set up, + // we can now issue a draw call for the mesh. + drawCommandEncoder->drawIndexed(mesh->indexCount, mesh->firstIndex); + } + } + + gSwapchain->present(); +} + +}; + +// This macro instantiates an appropriate main function to +// run the application defined above. +PLATFORM_UI_MAIN(innerMain<ModelViewer>) diff --git a/examples/experimental/model-viewer/shaders.slang b/examples/model-viewer/shaders.slang index 15ce0120d..0cc0d802f 100644 --- a/examples/experimental/model-viewer/shaders.slang +++ b/examples/model-viewer/shaders.slang @@ -345,25 +345,14 @@ struct PerModel ParameterBlock<PerModel> gModelParams; // We want our shader to work with any kind of lighting environment -// - that is, and type that implements `ILightEnv`. Furthermore, -// we want the parameters of that lighting environment to be passed -// as parameter block - `ParameterBlock<L>` for some type `L`. -// -// We handle this by defining a global generic type parameter for -// our shader, and constrainting it to implement `ILightEnv`... -// -type_param TLightEnv : ILightEnv; -// -// ... and then defining a parameter block that uses that type -// parameter as the "element type" of the block: -// -ParameterBlock<TLightEnv> gLightEnv; +// - that is, and type that implements `ILightEnv`. + +ILightEnv gLightEnv; // Our handling of the material parameter for our shader // is quite similar to the case for the lighting environment: // -type_param TMaterial : IMaterial; -ParameterBlock<TMaterial> gMaterial; +IMaterial gMaterial; // Our vertex shader entry point is only marginally more // complicated than the Hello World example. We will @@ -458,14 +447,14 @@ float4 fragmentMain( // a BRDF suitable for evaluating under illumination // from different light sources. // - // Note that the return type here is `TMaterial.BRDF`, + // Note that the return type here is `gMaterial.BRDF`, // which is the `BRDF` type *associated* with the (unknown) // `TMaterial` type. When `TMaterial` gets substituted for // a concrete type later (e.g., `SimpleMaterial`) this // will resolve to a concrete type too (e.g., `SimpleMaterial.BRDF` // which is an alias for `BlinnPhong`). // - TMaterial.BRDF brdf = gMaterial.prepare(g); + let brdf = gMaterial.prepare(g); // Now that we've done the first step of material evaluation // and sampled texture maps, etc., it is time to start diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp index 40c97e0f4..d94d629ba 100644 --- a/examples/shader-toy/main.cpp +++ b/examples/shader-toy/main.cpp @@ -76,21 +76,6 @@ struct Uniforms float iTime; }; -// 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 ) - { - printf("%s", (const char*) diagnosticsBlob->getBufferPointer()); - } -} - // The main interesting part of the host application code is where we // load, compile, inspect, and compose the Slang shader code. // diff --git a/premake5.lua b/premake5.lua index e7223a5f3..ef53ed47a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -647,6 +647,8 @@ example "gpu-printing" example "shader-toy" +example "model-viewer" + example "shader-object" kind "ConsoleApp" @@ -1228,6 +1230,10 @@ standardProject("slang", "source/slang") "{COPY} ../../../external/slang-binaries/bin/" .. targetName .. "/libslang-glslang.so %{cfg.targetdir}" } end + + filter {"configurations:debug"} + defines { "SLANG_ENABLE_IR_BREAK_ALLOC=1" } + filter {} if enableProfile then diff --git a/slang-gfx.h b/slang-gfx.h index c87c0adb6..6693809a3 100644 --- a/slang-gfx.h +++ b/slang-gfx.h @@ -1372,6 +1372,7 @@ public: const char* targetProfile = nullptr; // (optional) Target shader profile. If null this will be set to platform dependent default. SlangFloatingPointMode floatingPointMode = SLANG_FLOATING_POINT_MODE_DEFAULT; SlangOptimizationLevel optimizationLevel = SLANG_OPTIMIZATION_LEVEL_DEFAULT; + SlangTargetFlags targetFlags = 0; }; struct Desc @@ -609,7 +609,10 @@ extern "C" in the input source or specified via the `spAddEntryPoint` function in a single output module (library/source file). */ - SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM = 1 << 8 + SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM = 1 << 8, + + /* When set, will dump out the IR between intermediate compilation steps.*/ + SLANG_TARGET_FLAG_DUMP_IR = 1 << 9 }; /*! @@ -15,6 +15,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gpu-printing", "build\visua EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hello-world", "build\visual-studio\hello-world\hello-world.vcxproj", "{010BE414-ED5B-CF56-16C0-BD18027062C0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "model-viewer", "build\visual-studio\model-viewer\model-viewer.vcxproj", "{2F8724C6-1BC3-2730-84D5-3F277030D04A}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shader-object", "build\visual-studio\shader-object\shader-object.vcxproj", "{25512BFB-1138-EDF2-BA88-5310A64E6659}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shader-toy", "build\visual-studio\shader-toy\shader-toy.vcxproj", "{0FC5DE93-FBEA-A8FA-E430-2EC6D0F5CDC6}" @@ -117,6 +119,14 @@ Global {010BE414-ED5B-CF56-16C0-BD18027062C0}.Release|Win32.Build.0 = Release|Win32 {010BE414-ED5B-CF56-16C0-BD18027062C0}.Release|x64.ActiveCfg = Release|x64 {010BE414-ED5B-CF56-16C0-BD18027062C0}.Release|x64.Build.0 = Release|x64 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Debug|Win32.ActiveCfg = Debug|Win32 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Debug|Win32.Build.0 = Debug|Win32 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Debug|x64.ActiveCfg = Debug|x64 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Debug|x64.Build.0 = Debug|x64 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Release|Win32.ActiveCfg = Release|Win32 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Release|Win32.Build.0 = Release|Win32 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Release|x64.ActiveCfg = Release|x64 + {2F8724C6-1BC3-2730-84D5-3F277030D04A}.Release|x64.Build.0 = Release|x64 {25512BFB-1138-EDF2-BA88-5310A64E6659}.Debug|Win32.ActiveCfg = Debug|Win32 {25512BFB-1138-EDF2-BA88-5310A64E6659}.Debug|Win32.Build.0 = Debug|Win32 {25512BFB-1138-EDF2-BA88-5310A64E6659}.Debug|x64.ActiveCfg = Debug|x64 @@ -254,6 +264,7 @@ Global {37BED5B5-23FA-D81F-8C0C-F1167867813A} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {57C81DD3-4304-213D-AC16-39349871C957} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {010BE414-ED5B-CF56-16C0-BD18027062C0} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} + {2F8724C6-1BC3-2730-84D5-3F277030D04A} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {25512BFB-1138-EDF2-BA88-5310A64E6659} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {0FC5DE93-FBEA-A8FA-E430-2EC6D0F5CDC6} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {E145B2B8-CD13-A6BE-B6A7-16E5A2148223} = {F3AB4ED5-5F37-BC99-6848-3F8ED452189A} diff --git a/source/core/core.natvis b/source/core/core.natvis index 1087b4e6a..08446db8d 100644 --- a/source/core/core.natvis +++ b/source/core/core.natvis @@ -89,6 +89,18 @@ </Expand> </Type> + <Type Name="Slang::OrderedHashSet<*>"> + <DisplayString>{{ size={dict._count} }}</DisplayString> + <Expand> + <LinkedListItems> + <Size>dict._count</Size> + <HeadPointer>dict.kvPairs.head</HeadPointer> + <NextPointer>next</NextPointer> + <ValueNode>Value</ValueNode> + </LinkedListItems> + </Expand> + </Type> + <Type Name="Slang::RefPtr<*>"> <SmartPointer Usage="Minimal">pointer</SmartPointer> <DisplayString Condition="pointer == 0">empty</DisplayString> diff --git a/source/slang/slang-api.cpp b/source/slang/slang-api.cpp index bb0e2e174..8347b8c30 100644 --- a/source/slang/slang-api.cpp +++ b/source/slang/slang-api.cpp @@ -83,6 +83,12 @@ SLANG_API SlangResult slang_createGlobalSession( slang::IGlobalSession** outGlobalSession) { Slang::ComPtr<slang::IGlobalSession> globalSession; + +#ifdef SLANG_ENABLE_IR_BREAK_ALLOC + // Set inst debug alloc counter to 0 so IRInsts for stdlib always starts from a large value. + Slang::_debugGetIRAllocCounter() = 0x80000000; +#endif + SLANG_RETURN_ON_FAIL(slang_createGlobalSessionWithoutStdLib(apiVersion, globalSession.writeRef())); // If we have the embedded stdlib, load from that, else compile it @@ -106,6 +112,12 @@ SLANG_API SlangResult slang_createGlobalSession( } *outGlobalSession = globalSession.detach(); + +#ifdef SLANG_ENABLE_IR_BREAK_ALLOC + // Reset inst debug alloc counter to 0 so IRInsts for user code always starts from 0. + Slang::_debugGetIRAllocCounter() = 0; +#endif + return SLANG_OK; } diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp index 44cfebf13..19a5fddf8 100755 --- a/source/slang/slang-compiler.cpp +++ b/source/slang/slang-compiler.cpp @@ -2309,6 +2309,9 @@ SlangResult dissassembleDXILUsingDXC( sink, m_program); + backEndRequest->shouldDumpIR = + (m_targetReq->getTargetFlags() & SLANG_TARGET_FLAG_DUMP_IR) != 0; + return _createWholeProgramResult( backEndRequest, nullptr); @@ -2339,6 +2342,9 @@ SlangResult dissassembleDXILUsingDXC( sink, m_program); + backEndRequest->shouldDumpIR = + (m_targetReq->getTargetFlags() & SLANG_TARGET_FLAG_DUMP_IR) != 0; + return _createEntryPointResult( entryPointIndex, backEndRequest, diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 01b682a39..af870d02b 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -138,11 +138,14 @@ static void dumpIRIfEnabled( if(compileRequest->shouldDumpIR) { DiagnosticSinkWriter writer(compileRequest->getSink()); - + //FILE* f = nullptr; + //fopen_s(&f, (String("dump-") + label + ".txt").getBuffer(), "wt"); + //FileWriter writer(f, 0); IRDumpOptions options; options.sourceManager = compileRequest->getSourceManager(); dumpIR(irModule, options, label, &writer); + //fclose(f); } } @@ -309,8 +312,10 @@ Result linkAndOptimizeIR( // perform specialization of functions based on parameter // values that need to be compile-time constants. // + dumpIRIfEnabled(compileRequest, irModule, "BEFORE-SPECIALIZE"); if (!compileRequest->disableSpecialization) specializeModule(irModule); + dumpIRIfEnabled(compileRequest, irModule, "AFTER-SPECIALIZE"); eliminateDeadCode(irModule); @@ -319,7 +324,7 @@ Result linkAndOptimizeIR( // function pointers. dumpIRIfEnabled(compileRequest, irModule, "BEFORE-LOWER-GENERICS"); lowerGenerics(targetRequest, irModule, sink); - dumpIRIfEnabled(compileRequest, irModule, "LOWER-GENERICS"); + dumpIRIfEnabled(compileRequest, irModule, "AFTER-LOWER-GENERICS"); if (sink->getErrorCount() != 0) return SLANG_FAIL; diff --git a/source/slang/slang-ir-dce.cpp b/source/slang/slang-ir-dce.cpp index db01929a2..285e5100c 100644 --- a/source/slang/slang-ir-dce.cpp +++ b/source/slang/slang-ir-dce.cpp @@ -120,6 +120,15 @@ struct DeadCodeEliminationContext UInt operandCount = inst->getOperandCount(); for( UInt ii = 0; ii < operandCount; ++ii ) { + switch (inst->getOp()) + { + case kIROp_BoundInterfaceType: + if (inst->getOperand(ii)->getOp() == kIROp_WitnessTable) + continue; + break; + default: + break; + } markInstAsLive(inst->getOperand(ii)); } diff --git a/source/slang/slang-ir-lower-generic-function.cpp b/source/slang/slang-ir-lower-generic-function.cpp index 6e9754744..8cd292e49 100644 --- a/source/slang/slang-ir-lower-generic-function.cpp +++ b/source/slang/slang-ir-lower-generic-function.cpp @@ -26,6 +26,7 @@ namespace Slang return genericValue; auto genericParent = as<IRGeneric>(genericValue); SLANG_ASSERT(genericParent); + SLANG_ASSERT(genericParent->getDataType()); auto func = as<IRFunc>(findGenericReturnVal(genericParent)); if (!func) { @@ -37,7 +38,8 @@ namespace Slang return genericValue; } SLANG_ASSERT(func); - if (!func->isDefinition()) + // Do not lower intrinsic functions. + if (!func->isDefinition() || func->findDecoration<IRTargetIntrinsicDecoration>()) { sharedContext->loweredGenericFunctions[genericValue] = genericValue; return genericValue; @@ -47,7 +49,10 @@ namespace Slang builder.sharedBuilder = &sharedContext->sharedBuilderStorage; builder.setInsertBefore(genericParent); auto loweredFunc = cast<IRFunc>(cloneInstAndOperands(&cloneEnv, &builder, func)); - loweredFunc->setFullType(lowerGenericFuncType(&builder, cast<IRGeneric>(genericParent->getFullType()))); + auto loweredGenericType = + lowerGenericFuncType(&builder, cast<IRGeneric>(genericParent->getFullType())); + SLANG_ASSERT(loweredGenericType); + loweredFunc->setFullType(loweredGenericType); List<IRInst*> clonedParams; for (auto genericChild : genericParent->getFirstBlock()->getChildren()) diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index a983188b7..1f68623bb 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -1587,6 +1587,25 @@ namespace Slang value->sourceLoc = sourceLocInfo->sourceLoc; } +#if SLANG_ENABLE_IR_BREAK_ALLOC + SLANG_API uint32_t _slangIRAllocBreak = 0xFFFFFFFF; + uint32_t& _debugGetIRAllocCounter() + { + static uint32_t counter = 0; + return counter; + } + uint32_t _debugGetAndIncreaseInstCounter() + { + if (_slangIRAllocBreak != 0xFFFFFFFF && _debugGetIRAllocCounter() == _slangIRAllocBreak) + { +#if _WIN32 && defined(_MSC_VER) + __debugbreak(); +#endif + } + return _debugGetIRAllocCounter()++; + } +#endif + // Create an IR instruction/value and initialize it. // // In this case `argCount` and `args` represent the @@ -1622,6 +1641,10 @@ namespace Slang // TODO: Do we need to run ctor after zeroing? new(inst)T(); +#if SLANG_ENABLE_IR_BREAK_ALLOC + inst->_debugUID = _debugGetAndIncreaseInstCounter(); +#endif + inst->operandCount = (uint32_t)(fixedArgCount + varArgCount); inst->m_op = op; @@ -1677,6 +1700,10 @@ namespace Slang // TODO: Do we need to run ctor after zeroing? new (inst) IRInst; +#if SLANG_ENABLE_IR_BREAK_ALLOC + inst->_debugUID = _debugGetAndIncreaseInstCounter(); +#endif + inst->m_op = op; if (type) { @@ -2213,6 +2240,9 @@ namespace Slang SLANG_UNUSED(endCursor); new(inst) IRInst(); +#if SLANG_ENABLE_IR_BREAK_ALLOC + inst->_debugUID = _debugGetAndIncreaseInstCounter(); +#endif inst->m_op = op; inst->typeUse.usedValue = type; inst->operandCount = (uint32_t) operandCount; @@ -2300,6 +2330,9 @@ namespace Slang SLANG_UNUSED(endCursor); new(inst) IRInst(); +#if SLANG_ENABLE_IR_BREAK_ALLOC + inst->_debugUID = _debugGetAndIncreaseInstCounter(); +#endif inst->m_op = op; inst->typeUse.usedValue = type; inst->operandCount = (uint32_t)operandCount; @@ -3155,7 +3188,7 @@ namespace Slang // // We want to emit `makeExistential(getValueFromBoundInterface(value) : C, witnessTable)`. // - auto concreteType = cast<IRType>(slotArgs[0]); + auto concreteType = (IRType*)(slotArgs[0]); auto witnessTable = slotArgs[1]; if (slotArgs[0]->getOp() == kIROp_DynamicType) return value; @@ -4501,6 +4534,18 @@ namespace Slang return name; } + static void dumpDebugID(IRDumpContext* context, IRInst* inst) + { +#if SLANG_ENABLE_IR_BREAK_ALLOC + dump(context, "[#"); + dump(context, String(inst->_debugUID)); + dump(context, "]"); +#else + SLANG_UNUSED(context); + SLANG_UNUSED(inst); +#endif + } + static void dumpID( IRDumpContext* context, IRInst* inst) @@ -4520,9 +4565,8 @@ namespace Slang { dump(context, "_"); } + dumpDebugID(context, inst); } - - struct StringEncoder { @@ -4973,7 +5017,7 @@ namespace Slang } dump(context, opInfo.name); - + dumpDebugID(context, inst); dumpInstOperandList(context, inst); } diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index b27542424..9eb03c269 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -432,6 +432,12 @@ struct IRInst void removeAndDeallocateAllDecorationsAndChildren(); +#ifdef SLANG_ENABLE_IR_BREAK_ALLOC + // Unique allocation ID for this instruction since start of current process. + // Used to aid debugging only. + uint32_t _debugUID; +#endif + // The type of the result value of this instruction, // or `null` to indicate that the instruction has // no value. @@ -603,7 +609,14 @@ struct IRType : IRInst { IRType* getCanonicalType() { return this; } - IR_PARENT_ISA(Type) + // Hack: specialize can also be a type. We should consider using a + // separate `specializeType` op code for types so we can use the normal + // `IR_PARENT_ISA` macro here. + static bool isaImpl(IROp opIn) + { + const int op = (kIROpMeta_OpMask & opIn); + return (op >= kIROp_FirstType && op <= kIROp_LastType) || op == kIROp_Specialize; + } }; IRType* unwrapArray(IRType* type); @@ -1550,6 +1563,10 @@ bool isBuiltin(IRInst* inst); // Get the enclosuing function of an instruction. IRFunc* getParentFunc(IRInst* inst); +#if SLANG_ENABLE_IR_BREAK_ALLOC +uint32_t& _debugGetIRAllocCounter(); +#endif + } #endif diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 3c9256178..de5a07c0d 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -15,6 +15,7 @@ #include "slang-ir-strip.h" #include "slang-ir-validate.h" #include "slang-ir-string-hash.h" +#include "slang-ir-clone.h" #include "slang-mangle.h" #include "slang-type-layout.h" @@ -6098,23 +6099,32 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> auto entry = subBuilder->createInterfaceRequirementEntry( getInterfaceRequirementKey(requirementDecl), nullptr); - IRInst* requirementVal = ensureDecl(subContext, requirementDecl).val; - if (requirementVal) + if (auto inheritance = as<InheritanceDecl>(requirementDecl)) { - switch (requirementVal->getOp()) - { - case kIROp_Func: - case kIROp_Generic: + auto irBaseType = lowerType(context, inheritance->base.type); + auto irWitnessTableType = subBuilder->getWitnessTableType(irBaseType); + entry->setRequirementVal(irWitnessTableType); + } + else + { + IRInst* requirementVal = ensureDecl(subContext, requirementDecl).val; + if (requirementVal) { - // Remove lowered `IRFunc`s since we only care about - // function types. - auto reqType = requirementVal->getFullType(); - entry->setRequirementVal(reqType); - break; - } - default: - entry->setRequirementVal(requirementVal); - break; + switch (requirementVal->getOp()) + { + case kIROp_Func: + case kIROp_Generic: + { + // Remove lowered `IRFunc`s since we only care about + // function types. + auto reqType = requirementVal->getFullType(); + entry->setRequirementVal(reqType); + break; + } + default: + entry->setRequirementVal(requirementVal); + break; + } } } irInterface->setOperand(entryIndex, entry); @@ -6598,6 +6608,34 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> return nullptr; } + static bool isChildOf(IRInst* child, IRInst* parent) + { + while (child && child->getParent() != parent) + child = child->getParent(); + return child != nullptr; + } + static void markInstsToClone(HashSet<IRInst*>& valuesToClone, IRInst* parentBlock, IRInst* value) + { + if (!isChildOf(value, parentBlock)) + return; + if (valuesToClone.Add(value)) + { + for (UInt i = 0; i < value->getOperandCount(); i++) + { + auto operand = value->getOperand(i); + markInstsToClone(valuesToClone, parentBlock, operand); + } + } + for (auto child : value->getChildren()) + markInstsToClone(valuesToClone, parentBlock, child); + auto parent = parentBlock->getParent(); + while (parent && parent != parentBlock) + { + valuesToClone.Add(parent); + parent = parent->getParent(); + } + } + // If any generic declarations have been created by `emitOuterGenerics`, // then finish them off by emitting `return` instructions for the // values that they should produce. @@ -6612,9 +6650,94 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> IRGeneric* parentGeneric) { IRInst* v = val; + + IRInst* returnType = v->getFullType(); + while (parentGeneric) { + // Create a universal type in `outterBlock` that will be used + // as the type of this generic inst. The return value of the + // generic inst will have a specialized type. + // For example, if we have a generic function + // g0 = generic<T> { return f: T->int } + // The type for `g0` should be: + // g0Type = generic<T1> { return IRFuncType{T1->int} } + // with `g0Type`, we can rewrite `g0` into: + // ``` + // g0 : g0Type = generic<T> + // { + // ftype = specialize(g0Type, T); + // return f : ftype; + // } + // ``` + IRBuilder typeBuilder; + typeBuilder.sharedBuilder = subBuilder->sharedBuilder; + IRCloneEnv cloneEnv = {}; + if (returnType) + { + HashSet<IRInst*> valuesToClone; + markInstsToClone(valuesToClone, parentGeneric->getFirstBlock(), returnType); + if (valuesToClone.Count() == 0) + { + // If returnType is independent of generic parameters, set + // the generic inst's type to just `returnType`. + parentGeneric->setFullType((IRType*)returnType); + } + else + { + // In the general case, we need to construct a separate + // generic value for the return type, and set the generic's type + // to the newly construct generic value. + typeBuilder.setInsertBefore(parentGeneric); + auto typeGeneric = typeBuilder.emitGeneric(); + typeBuilder.setInsertInto(typeGeneric); + typeBuilder.emitBlock(); + + for (auto child : parentGeneric->getFirstBlock()->getChildren()) + { + if (valuesToClone.Contains(child)) + { + cloneInst(&cloneEnv, &typeBuilder, child); + } + } + IRInst* clonedReturnType = nullptr; + cloneEnv.mapOldValToNew.TryGetValue(returnType, clonedReturnType); + SLANG_ASSERT(clonedReturnType); + typeBuilder.emitReturn(clonedReturnType); + parentGeneric->setFullType((IRType*)typeGeneric); + returnType = typeGeneric; + } + } + subBuilder->setInsertInto(parentGeneric->getFirstBlock()); +#if 0 + // TODO: we cannot enable this right now as it breaks too many existing code + // that is assuming a generic function type is `IRFuncType` rather than `IRSpecialize`. + if (v->getFullType() != returnType) + { + // We need to rewrite the type of the return value as + // `specialize(returnType, ...)`. + SLANG_ASSERT(returnType->getOp() == kIROp_Generic); + auto oldType = v->getFullType(); + SLANG_ASSERT(isChildOf(oldType, parentGeneric->getFirstBlock())); + + List<IRInst*> specializeArgs; + for (auto param : parentGeneric->getParams()) + { + IRInst* arg = nullptr; + if (cloneEnv.mapOldValToNew.TryGetValue(param, arg)) + { + specializeArgs.add(arg); + } + } + auto specializedType = subBuilder->emitSpecializeInst( + subBuilder->getTypeKind(), + returnType, + (UInt)specializeArgs.getCount(), + specializeArgs.getBuffer()); + oldType->replaceUsesWith(specializedType); + } +#endif subBuilder->emitReturn(v); parentGeneric->moveToEnd(); @@ -7261,11 +7384,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // If this function is defined inside an interface, add a reference to the IRFunc from // the interface's type definition. auto finalVal = finishOuterGenerics(subBuilder, irFunc, outerGeneric); - if (auto genericVal = as<IRGeneric>(finalVal)) - { - auto funcType = lowerFuncType(decl); - genericVal->setFullType((IRType*)funcType); - } return LoweredValInfo::simple(finalVal); } diff --git a/source/slang/slang-syntax.cpp b/source/slang/slang-syntax.cpp index accbdb27e..d5f2c13db 100644 --- a/source/slang/slang-syntax.cpp +++ b/source/slang/slang-syntax.cpp @@ -30,7 +30,10 @@ void printDiagnosticArg(StringBuilder& sb, Val* val) void printDiagnosticArg(StringBuilder& sb, TypeExp const& type) { - type.type->toText(sb); + if (type.type) + type.type->toText(sb); + else + sb << "<null>"; } void printDiagnosticArg(StringBuilder& sb, QualType const& type) diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index cb272f974..ea010c187 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -72,9 +72,10 @@ </Expand> </Type> <Type Name="Slang::IRInst"> - <DisplayString>{{{m_op}}}</DisplayString> + <DisplayString>{{{m_op} #{_debugUID}}}</DisplayString> <Expand> <Item Name="[op]">m_op</Item> + <Item Name="[UID]">_debugUID</Item> <Item Name="[type]">typeUse.usedValue</Item> <CustomListItems MaxItemsPerView="3"> <Variable Name="child" InitialValue="m_decorationsAndChildren.first"/> diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp index 15acb680c..e02f7d656 100644 --- a/tools/gfx/d3d12/render-d3d12.cpp +++ b/tools/gfx/d3d12/render-d3d12.cpp @@ -4804,6 +4804,7 @@ Result D3D12Device::createProgram(const IShaderProgram::Desc& desc, IShaderProgr (SlangInt)i, 0, kernelCode.writeRef(), diagnostics.writeRef()); if (diagnostics) { + printf("%s\n", diagnostics->getBufferPointer()); // TODO: report compile error. } SLANG_RETURN_ON_FAIL(compileResult); diff --git a/tools/gfx/slang-context.h b/tools/gfx/slang-context.h index d5bf261e4..be6539da5 100644 --- a/tools/gfx/slang-context.h +++ b/tools/gfx/slang-context.h @@ -34,6 +34,7 @@ namespace gfx targetDesc.profile = globalSession->findProfile(targetProfile); targetDesc.optimizationLevel = desc.optimizationLevel; targetDesc.floatingPointMode = desc.floatingPointMode; + targetDesc.flags = desc.targetFlags; slangSessionDesc.targetCount = 1; slangSessionDesc.targets = &targetDesc; SLANG_RETURN_ON_FAIL(globalSession->createSession(slangSessionDesc, session.writeRef())); diff --git a/tools/platform/model.cpp b/tools/platform/model.cpp index 54c171e54..fadfcc0e2 100644 --- a/tools/platform/model.cpp +++ b/tools/platform/model.cpp @@ -20,7 +20,10 @@ #include <unordered_map> #include <unordered_set> -namespace gfx { +namespace platform { + +using namespace gfx; +using namespace Slang; // TinyObj provides a tuple type that bundles up indices, but doesn't // provide equality comparison or hashing for that type. We'd like @@ -70,11 +73,11 @@ bool operator==(SmoothingGroupVertexID const& left, SmoothingGroupVertexID const namespace std { - template<> struct hash<gfx::ObjIndexKey> + template<> struct hash<platform::ObjIndexKey> { - size_t operator()(gfx::ObjIndexKey const& key) const + size_t operator()(platform::ObjIndexKey const& key) const { - gfx::Hasher hasher; + platform::Hasher hasher; hasher.add(key.index.vertex_index); hasher.add(key.index.normal_index); hasher.add(key.index.texcoord_index); @@ -82,11 +85,11 @@ namespace std } }; - template<> struct hash<gfx::SmoothingGroupVertexID> + template <> struct hash<platform::SmoothingGroupVertexID> { - size_t operator()(gfx::SmoothingGroupVertexID const& id) const + size_t operator()(platform::SmoothingGroupVertexID const& id) const { - gfx::Hasher hasher; + platform::Hasher hasher; hasher.add(id.smoothingGroup); hasher.add(id.positionID); return hasher.state; @@ -94,7 +97,7 @@ namespace std }; } -namespace gfx +namespace platform { ComPtr<ITextureResource> loadTextureImage( @@ -202,7 +205,7 @@ static std::string makeString(const char* start, const char* end) return std::string(start, size_t(end - start)); } -Result ModelLoader::load( +SlangResult ModelLoader::load( char const* inputPath, void** outModel) { diff --git a/tools/platform/model.h b/tools/platform/model.h index 8cff2c67d..b4aff9273 100644 --- a/tools/platform/model.h +++ b/tools/platform/model.h @@ -7,7 +7,9 @@ #include <vector> #include <string> -namespace gfx { +#include "platform-api.h" + +namespace platform { struct ModelLoader { @@ -17,7 +19,7 @@ struct ModelLoader glm::vec3 specularColor; float specularity; - ComPtr<ITextureResource> diffuseMap; + Slang::ComPtr<gfx::ITextureResource> diffuseMap; }; struct Vertex @@ -39,9 +41,9 @@ struct ModelLoader struct ModelData { - ComPtr<IBufferResource> vertexBuffer; - ComPtr<IBufferResource> indexBuffer; - PrimitiveTopology primitiveTopology; + Slang::ComPtr<gfx::IBufferResource> vertexBuffer; + Slang::ComPtr<gfx::IBufferResource> indexBuffer; + gfx::PrimitiveTopology primitiveTopology; int vertexCount; int indexCount; int meshCount; @@ -65,12 +67,12 @@ struct ModelLoader FlipWinding = 1 << 0, }; - ICallbacks* callbacks = nullptr; - Slang::ComPtr<IDevice> device; - LoadFlags loadFlags = 0; - float scale = 1.0f; + ICallbacks* callbacks = nullptr; + gfx::IDevice* device; + LoadFlags loadFlags = 0; + float scale = 1.0f; - Result load(char const* inputPath, void** outModel); + SLANG_PLATFORM_API SlangResult load(char const* inputPath, void** outModel); }; diff --git a/tools/platform/platform-api.h b/tools/platform/platform-api.h new file mode 100644 index 000000000..b04e5ffce --- /dev/null +++ b/tools/platform/platform-api.h @@ -0,0 +1,23 @@ +#ifndef SLANG_PLATFORM_API_H +#define SLANG_PLATFORM_API_H + +#if defined(SLANG_PLATFORM_DYNAMIC) +# if defined(_MSC_VER) +# ifdef SLANG_PLATFORM_DYNAMIC_EXPORT +# define SLANG_PLATFORM_API SLANG_DLL_EXPORT +# else +# define SLANG_PLATFORM_API __declspec(dllimport) +# endif +# else +// TODO: need to consider compiler capabilities +//# ifdef SLANG_DYNAMIC_EXPORT +# define SLANG_PLATFORM_API SLANG_DLL_EXPORT +//# endif +# endif +#endif + +#ifndef SLANG_PLATFORM_API +# define SLANG_PLATFORM_API +#endif + +#endif diff --git a/tools/platform/window.h b/tools/platform/window.h index 65e0508b6..f80e21302 100644 --- a/tools/platform/window.h +++ b/tools/platform/window.h @@ -5,24 +5,7 @@ #include "source/core/slang-basic.h" #include "source/core/slang-func-ptr.h" -#if defined(SLANG_PLATFORM_DYNAMIC) -# if defined(_MSC_VER) -# ifdef SLANG_PLATFORM_DYNAMIC_EXPORT -# define SLANG_PLATFORM_API SLANG_DLL_EXPORT -# else -# define SLANG_PLATFORM_API __declspec(dllimport) -# endif -# else -// TODO: need to consider compiler capabilities -//# ifdef SLANG_DYNAMIC_EXPORT -# define SLANG_PLATFORM_API SLANG_DLL_EXPORT -//# endif -# endif -#endif - -#ifndef SLANG_PLATFORM_API -# define SLANG_PLATFORM_API -#endif +#include "platform-api.h" namespace platform { |
