From 2886bc35e7b023370a8b8d56d78e5335eee2eb98 Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 16 Apr 2021 10:35:42 -0700 Subject: Add Hello world example. (#1797) --- .../visual-studio/hello-world/hello-world.vcxproj | 22 +- .../hello-world/hello-world.vcxproj.filters | 13 +- build/visual-studio/triangle/triangle.vcxproj | 193 ++++++ .../triangle/triangle.vcxproj.filters | 18 + docs/user-guide/01-get-started.md | 6 +- examples/hello-world/README.md | 13 +- examples/hello-world/hello-world.slang | 12 + examples/hello-world/main.cpp | 739 ++++++++++++--------- examples/hello-world/shaders.slang | 66 -- examples/hello-world/vulkan-api.cpp | 228 +++++++ examples/hello-world/vulkan-api.h | 132 ++++ examples/triangle/README.md | 12 + examples/triangle/main.cpp | 398 +++++++++++ examples/triangle/shaders.slang | 66 ++ premake5.lua | 4 + slang.sln | 11 + 16 files changed, 1516 insertions(+), 417 deletions(-) create mode 100644 build/visual-studio/triangle/triangle.vcxproj create mode 100644 build/visual-studio/triangle/triangle.vcxproj.filters create mode 100644 examples/hello-world/hello-world.slang delete mode 100644 examples/hello-world/shaders.slang create mode 100644 examples/hello-world/vulkan-api.cpp create mode 100644 examples/hello-world/vulkan-api.h create mode 100644 examples/triangle/README.md create mode 100644 examples/triangle/main.cpp create mode 100644 examples/triangle/shaders.slang diff --git a/build/visual-studio/hello-world/hello-world.vcxproj b/build/visual-studio/hello-world/hello-world.vcxproj index 7164fd9e6..181e1de40 100644 --- a/build/visual-studio/hello-world/hello-world.vcxproj +++ b/build/visual-studio/hello-world/hello-world.vcxproj @@ -98,13 +98,13 @@ NotUsing Level3 _DEBUG;%(PreprocessorDefinitions) - ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\tools;..\..\..\external\vulkan\include;%(AdditionalIncludeDirectories) EditAndContinue Disabled MultiThreadedDebug - Windows + Console true @@ -113,13 +113,13 @@ NotUsing Level3 _DEBUG;%(PreprocessorDefinitions) - ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\tools;..\..\..\external\vulkan\include;%(AdditionalIncludeDirectories) EditAndContinue Disabled MultiThreadedDebug - Windows + Console true @@ -128,7 +128,7 @@ NotUsing Level3 NDEBUG;%(PreprocessorDefinitions) - ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\tools;..\..\..\external\vulkan\include;%(AdditionalIncludeDirectories) Full true true @@ -137,7 +137,7 @@ MultiThreaded - Windows + Console true true @@ -147,7 +147,7 @@ NotUsing Level3 NDEBUG;%(PreprocessorDefinitions) - ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\tools;..\..\..\external\vulkan\include;%(AdditionalIncludeDirectories) Full true true @@ -156,16 +156,20 @@ MultiThreaded - Windows + Console true true + + + + - + diff --git a/build/visual-studio/hello-world/hello-world.vcxproj.filters b/build/visual-studio/hello-world/hello-world.vcxproj.filters index 9399d6c61..3c5e0c8dc 100644 --- a/build/visual-studio/hello-world/hello-world.vcxproj.filters +++ b/build/visual-studio/hello-world/hello-world.vcxproj.filters @@ -1,17 +1,28 @@ + + {21EB8090-0D4E-1035-B6D3-48EBA215DCB7} + {E9C7FDCE-D52A-8D73-7EB0-C5296AF258F6} + + + Header Files + + Source Files + + Source Files + - + Source Files diff --git a/build/visual-studio/triangle/triangle.vcxproj b/build/visual-studio/triangle/triangle.vcxproj new file mode 100644 index 000000000..5a6915713 --- /dev/null +++ b/build/visual-studio/triangle/triangle.vcxproj @@ -0,0 +1,193 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3BB99068-27C9-3C39-9082-A1577CB12BD2} + true + Win32Proj + triangle + + + + Application + true + Unicode + v140 + + + Application + true + Unicode + v140 + + + Application + false + Unicode + v140 + + + Application + false + Unicode + v140 + + + + + + + + + + + + + + + + + + + true + ..\..\..\bin\windows-x86\debug\ + ..\..\..\intermediate\windows-x86\debug\triangle\ + triangle + .exe + + + true + ..\..\..\bin\windows-x64\debug\ + ..\..\..\intermediate\windows-x64\debug\triangle\ + triangle + .exe + + + false + ..\..\..\bin\windows-x86\release\ + ..\..\..\intermediate\windows-x86\release\triangle\ + triangle + .exe + + + false + ..\..\..\bin\windows-x64\release\ + ..\..\..\intermediate\windows-x64\release\triangle\ + triangle + .exe + + + + NotUsing + Level3 + _DEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + EditAndContinue + Disabled + MultiThreadedDebug + + + Windows + true + + + + + NotUsing + Level3 + _DEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + EditAndContinue + Disabled + MultiThreadedDebug + + + Windows + true + + + + + NotUsing + Level3 + NDEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + Full + true + true + false + true + MultiThreaded + + + Windows + true + true + + + + + NotUsing + Level3 + NDEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\tools;%(AdditionalIncludeDirectories) + Full + true + true + false + true + MultiThreaded + + + Windows + true + true + + + + + + + + + + + {37BED5B5-23FA-D81F-8C0C-F1167867813A} + + + {DB00DA62-0533-4AFD-B59F-A67D5B3A0808} + + + {222F7498-B40C-4F3F-A704-DDEB91A4484A} + + + {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} + + + {3565FE5E-4FA3-11EB-AE93-0242AC130002} + + + {F9BE7957-8399-899E-0C49-E714FDDD4B65} + + + + + + \ No newline at end of file diff --git a/build/visual-studio/triangle/triangle.vcxproj.filters b/build/visual-studio/triangle/triangle.vcxproj.filters new file mode 100644 index 000000000..c5d6a7793 --- /dev/null +++ b/build/visual-studio/triangle/triangle.vcxproj.filters @@ -0,0 +1,18 @@ + + + + + {E9C7FDCE-D52A-8D73-7EB0-C5296AF258F6} + + + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/docs/user-guide/01-get-started.md b/docs/user-guide/01-get-started.md index 1e3fd740f..eafc599f9 100644 --- a/docs/user-guide/01-get-started.md +++ b/docs/user-guide/01-get-started.md @@ -94,8 +94,4 @@ Note that in the generated GLSL code, all shader parameters are qualified with e ## The full example -> #### Note #### -> The current `hello-world` implementation does not use raw Vulkan API calls; -> this will be addressed in a future revision so that it matches the documentation. - -The full Vulkan example that sets up and runs the `hello-world.slang` shader in located in the `/examples/hello-world` directory of the Slang repository. The example code initializes a Vulkan context and runs the compiled SPIRV code. +The full Vulkan example that sets up and runs the `hello-world.slang` shader in located in the [/examples/hello-world](https://github.com/shader-slang/slang/tree/master/examples/hello-world) directory of the Slang repository. The example code initializes a Vulkan context and runs the compiled SPIRV code. The example code demonstrates how to use the Slang API to load and compile shaders. diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index ba377b8cb..2bd6d4b45 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -1,12 +1,5 @@ Slang "Hello World" Example -=========================== +========================== -The goal of this example is to demonstrate an almost minimal application that uses Slang for shading. - -The `shaders.slang` file contains simple vertex and fragment shader entry points. The shader code should compile as either Slang or HLSL code (that is, this example does not show off any new Slang language features). - -The `main.cpp` file contains the C++ application code, showing how to use the Slang API to load and compile the shader code to DirectX shader bytecode (DXBC). -The application perform rendering using the D3D11 API, through a platform and graphics API abstraction layer that is implemented in `tools/gfx`. -Note that this abstraction layer is *not* required in order to work with Slang, and it is just there to help us write example and test applications more conveniently. - -This example is not necessarily representative of best practices for integrating Slang into a production engine; the goal is merely to use the minimum amount of code possible to demonstrate a complete applicaiton that uses Slang. +This example shows how to use the Slang API to compile a simple +compute shader written in Slang into SPIRV and run it in Vulkan. \ No newline at end of file diff --git a/examples/hello-world/hello-world.slang b/examples/hello-world/hello-world.slang new file mode 100644 index 000000000..565924035 --- /dev/null +++ b/examples/hello-world/hello-world.slang @@ -0,0 +1,12 @@ +// hello-world.slang +StructuredBuffer buffer0; +StructuredBuffer buffer1; +RWStructuredBuffer result; + +[shader("compute")] +[numthreads(1,1,1)] +void computeMain(uint3 threadId : SV_DispatchThreadID) +{ + uint index = threadId.x; + result[index] = buffer0[index] + buffer1[index]; +} diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index 6b9104072..da149a8e0 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -1,137 +1,157 @@ // main.cpp -// This file implements an extremely simple example of loading and -// executing a Slang shader program. This is primarily an example -// of how to use Slang as a "drop-in" replacement for an existing -// HLSL compiler like the `D3DCompile` API. More advanced usage -// of advanced Slang language and API features is left to the -// next example. +// This file provides the application code for the `hello-world` example. // -// The comments in the file will attempt to explain concepts as -// they are introduced. -// -// Of course, in order to use the Slang API, we need to include -// its header. We have set up the build options for this project -// so that it is as simple as: + +// This example uses Vulkan to run a simple compute shader written in Slang. +// The goal is to demonstrate how to use the Slang API to cross compile +// shader code. // #include -// -// Other build setups are possible, and Slang doesn't assume that -// its include directory must be added to your global include -// path. - -// For the purposes of keeping the demo code as simple as possible, -// while still retaining some level of portability, our examples -// make use of a small platform and graphics API abstraction layer, -// which is included in the Slang source distribution under the -// `tools/` directory. -// -// Applications can of course use Slang without ever touching this -// abstraction layer, so we will not focus on it when explaining -// examples, except in places where best practices for interacting -// with Slang may depend on an application/engine making certain -// design choices in their abstraction layer. -// -#include "slang-gfx.h" -#include "gfx-util/shader-cursor.h" -#include "tools/platform/window.h" -#include "slang-com-ptr.h" -#include "source/core/slang-basic.h" +#include + +#include "vulkan-api.h" #include "examples/example-base/example-base.h" -using namespace gfx; -using namespace Slang; +using Slang::ComPtr; -// For the purposes of a small example, we will define the vertex data for a -// single triangle directly in the source file. It should be easy to extend -// this example to load data from an external source, if desired. -// -struct Vertex +struct HelloWorldExample { - float position[3]; - float color[3]; + // The Vulkan functions pointers result from loading the vulkan library. + VulkanAPI vkAPI; + + // Vulkan objects used in this example. + VkQueue queue; + VkCommandPool commandPool = VK_NULL_HANDLE; + + // Input and output buffers. + VkBuffer inOutBuffers[3] = {}; + VkDeviceMemory bufferMemories[3] = {}; + + const size_t inputElementCount = 16; + const size_t bufferSize = sizeof(float) * inputElementCount; + + // We use a staging buffer allocated on host-visible memory to + // upload/download data from GPU. + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VkDeviceMemory stagingMemory = VK_NULL_HANDLE; + + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + + // Initializes the Vulkan instance and device. + int initVulkanInstanceAndDevice(); + + // This function contains the most interesting part of this example. + // It loads the `hello-world.slang` shader and compile it using the Slang API + // into a SPIRV module, then create a Vulkan pipeline from the compiled shader. + int createComputePipelineFromShader(); + + // Creates the input and output buffers. + int createInOutBuffers(); + + // Sets up descriptor set bindings and dispatches the compute task. + int dispatchCompute(); + + // Reads back and prints the result of the compute task. + int printComputeResults(); + + // Main logic of this example. + int run(); + + ~HelloWorldExample(); + }; -static const int kVertexCount = 3; -static const Vertex kVertexData[kVertexCount] = +int main() { - { { 0, 0, 0.5 }, { 1, 0, 0 } }, - { { 0, 1, 0.5 }, { 0, 0, 1 } }, - { { 1, 0, 0.5 }, { 0, 1, 0 } }, -}; + HelloWorldExample example; + return example.run(); +} -// The example application will be implemented as a `struct`, so that -// we can scope the resources it allocates without using global variables. -// -struct HelloWorld : public WindowedAppBase +/************************************************************/ +/* HelloWorldExample Implementation */ +/************************************************************/ + +int HelloWorldExample::run() { + RETURN_ON_FAIL(initVulkanInstanceAndDevice()); + RETURN_ON_FAIL(createComputePipelineFromShader()); + RETURN_ON_FAIL(createInOutBuffers()); + RETURN_ON_FAIL(dispatchCompute()); + RETURN_ON_FAIL(printComputeResults()); + return 0; +} -// 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) +int HelloWorldExample::initVulkanInstanceAndDevice() { - if( diagnosticsBlob != nullptr ) + if (initializeVulkanDevice(vkAPI) != 0) { - printf("%s", (const char*) diagnosticsBlob->getBufferPointer()); + printf("Failed to load Vulkan.\n"); + return -1; } + + VkCommandPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolCreateInfo.queueFamilyIndex = vkAPI.queueFamilyIndex; + RETURN_ON_FAIL(vkAPI.vkCreateCommandPool( + vkAPI.device, &poolCreateInfo, nullptr, &commandPool)); + + vkAPI.vkGetDeviceQueue(vkAPI.device, vkAPI.queueFamilyIndex, 0, &queue); + return 0; } -// The main task an application cares about is compiling shader code -// from souce (if needed) and loading it through the chosen graphics API. -// -// In addition, an application may want to receive reflection information -// about the program, which is what a `slang::ProgramLayout` provides. -// -gfx::Result loadShaderProgram( - gfx::IDevice* device, - gfx::IShaderProgram** outProgram) +int HelloWorldExample::createComputePipelineFromShader() { - // We need to obatin a compilation session (`slang::ISession`) that will provide - // a scope to all the compilation and loading of code we do. - // - // Our example application uses the `gfx` graphics API abstraction layer, which already - // creates a Slang compilation session for us, so we just grab and use it here. - ComPtr slangSession; - slangSession = device->getSlangSession(); + // First we need to create slang global session with work with the Slang API. + ComPtr slangGlobalSession; + RETURN_ON_FAIL(slang::createGlobalSession(slangGlobalSession.writeRef())); + + // Next we create a compilation session to generate SPIRV code from Slang source. + slang::SessionDesc sessionDesc = {}; + slang::TargetDesc targetDesc = {}; + targetDesc.format = SLANG_SPIRV; + targetDesc.profile = slangGlobalSession->findProfile("glsl440"); + sessionDesc.targetCount = 1; + sessionDesc.targets = &targetDesc; + + ComPtr session; + RETURN_ON_FAIL(slangGlobalSession->createSession(sessionDesc, session.writeRef())); - // We can now start loading code into the slang session. + // Once the session has been obtained, we can start loading code into it. // // The simplest way to load code is by calling `loadModule` with the name of a Slang - // module. A call to `loadModule("MyStuff")` will behave more or less as if you + // module. A call to `loadModule("hello-world")` will behave more or less as if you // wrote: // - // import MyStuff; + // import hello_world; // // In a Slang shader file. The compiler will use its search paths to try to locate - // `MyModule.slang`, then compile and load that file. If a matching module had + // `hello-world.slang`, then compile and load that file. If a matching module had // already been loaded previously, that would be used directly. - // - ComPtr diagnosticsBlob; - slang::IModule* module = slangSession->loadModule("shaders", diagnosticsBlob.writeRef()); - diagnoseIfNeeded(diagnosticsBlob); - if(!module) - return SLANG_FAIL; + slang::IModule* slangModule = nullptr; + { + ComPtr diagnosticBlob; + slangModule = session->loadModule("hello-world", diagnosticBlob.writeRef()); + diagnoseIfNeeded(diagnosticBlob); + if (!slangModule) + return -1; + } - // Loading the `shaders` module will compile and check all the shader code in it, + // Loading the `hello-world` module will compile and check all the shader code in it, // including the shader entry points we want to use. Now that the module is loaded // we can look up those entry points by name. // // Note: If you are using this `loadModule` approach to load your shader code it is // important to tag your entry point functions with the `[shader("...")]` attribute - // (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there + // (e.g., `[shader("compute")] void computeMain(...)`). Without that information there // is no umambiguous way for the compiler to know which functions represent entry // points when it parses your code via `loadModule()`. // - ComPtr vertexEntryPoint; - SLANG_RETURN_ON_FAIL(module->findEntryPointByName("vertexMain", vertexEntryPoint.writeRef())); - // - ComPtr fragmentEntryPoint; - SLANG_RETURN_ON_FAIL(module->findEntryPointByName("fragmentMain", fragmentEntryPoint.writeRef())); + ComPtr entryPoint; + slangModule->findEntryPointByName("computeMain", entryPoint.writeRef()); // At this point we have a few different Slang API objects that represent // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. @@ -147,18 +167,8 @@ gfx::Result loadShaderProgram( // and entry points. // Slang::List componentTypes; - componentTypes.add(module); - - // Later on when we go to extract compiled kernel code for our vertex - // and fragment shaders, we will need to make use of their order within - // the composition, so we will record the relative ordering of the entry - // points here as we add them. - int entryPointCount = 0; - int vertexEntryPointIndex = entryPointCount++; - componentTypes.add(vertexEntryPoint); - - int fragmentEntryPointIndex = entryPointCount++; - componentTypes.add(fragmentEntryPoint); + componentTypes.add(slangModule); + componentTypes.add(entryPoint); // Actually creating the composite component type is a single operation // on the Slang session, but the operation could potentially fail if @@ -166,233 +176,310 @@ gfx::Result loadShaderProgram( // combine multiple copies of the same module), so we need to deal // with the possibility of diagnostic output. // - ComPtr linkedProgram; - SlangResult result = slangSession->createCompositeComponentType( - componentTypes.getBuffer(), - componentTypes.getCount(), - linkedProgram.writeRef(), - diagnosticsBlob.writeRef()); - diagnoseIfNeeded(diagnosticsBlob); - SLANG_RETURN_ON_FAIL(result); - - // Once we've described the particular composition of entry points - // that we want to compile, we defer to the graphics API layer - // to extract compiled kernel code and load it into the API-specific - // program representation. - // - gfx::IShaderProgram::Desc programDesc = {}; - programDesc.pipelineType = gfx::PipelineType::Graphics; - programDesc.slangProgram = linkedProgram; - SLANG_RETURN_ON_FAIL(device->createProgram(programDesc, outProgram)); - - return SLANG_OK; -} + ComPtr composedProgram; + { + ComPtr diagnosticsBlob; + SlangResult result = session->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + composedProgram.writeRef(), + diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + RETURN_ON_FAIL(result); + } -// -// The above function shows the core of what is required to use the -// Slang API as a simple compiler (e.g., a drop-in replacement for -// fxc or dxc). -// -// The rest of this file implements an extremely simple rendering application -// that will execute the vertex/fragment shaders loaded with the function -// we have just defined. -// + // Now we can call `composedProgram->getEntryPointCode()` to retrieve the + // compiled SPIRV code that we will use to create a vulkan compute pipeline. + // This will trigger the final Slang compilation and spirv code generation. + ComPtr spirvCode; + { + ComPtr diagnosticsBlob; + SlangResult result = composedProgram->getEntryPointCode( + 0, 0, spirvCode.writeRef(), diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + RETURN_ON_FAIL(result); + } -// We will define global variables for the various platform and -// graphics API objects that our application needs: -// -// As a reminder, *none* of these are Slang API objects. All -// of them come from the utility library we are using to simplify -// building an example program. -// -ComPtr gPipelineState; -ComPtr gVertexBuffer; + // The following steps are all Vulkan API calls to create a pipeline. + + // First we need to create a descriptor set layout and a pipeline layout. + // In this example, the pipeline layout is simple: we have a single descriptor + // set with three buffer descriptors for our input/output storage buffers. + // General applications typically has much more complicated pipeline layouts, + // and should consider using Slang's reflection API to learn about the shader + // parameter layout of a shader program. However, Slang's reflection API is + // out of scope of this example. + VkDescriptorSetLayoutCreateInfo descSetLayoutCreateInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + descSetLayoutCreateInfo.bindingCount = 3; + VkDescriptorSetLayoutBinding bindings[3]; + for (int i = 0; i < 3; i++) + { + auto& binding = bindings[i]; + binding.binding = i; + binding.descriptorCount = 1; + binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + binding.stageFlags = VK_SHADER_STAGE_ALL; + binding.pImmutableSamplers = nullptr; + } + descSetLayoutCreateInfo.pBindings = bindings; + RETURN_ON_FAIL(vkAPI.vkCreateDescriptorSetLayout( + vkAPI.device, &descSetLayoutCreateInfo, nullptr, &descriptorSetLayout)); + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipelineLayoutCreateInfo.setLayoutCount = 1; + pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout; + RETURN_ON_FAIL(vkAPI.vkCreatePipelineLayout( + vkAPI.device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + // Next we create a shader module from the compiled SPIRV code. + VkShaderModuleCreateInfo shaderCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + shaderCreateInfo.codeSize = spirvCode->getBufferSize(); + shaderCreateInfo.pCode = static_cast(spirvCode->getBufferPointer()); + VkShaderModule vkShaderModule; + RETURN_ON_FAIL( + vkAPI.vkCreateShaderModule(vkAPI.device, &shaderCreateInfo, nullptr, &vkShaderModule)); + + // Now we have all we need to create a compute pipeline. + VkComputePipelineCreateInfo pipelineCreateInfo = { + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineCreateInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + pipelineCreateInfo.stage.module = vkShaderModule; + pipelineCreateInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineCreateInfo.stage.pName = "main"; + pipelineCreateInfo.layout = pipelineLayout; + RETURN_ON_FAIL(vkAPI.vkCreateComputePipelines( + vkAPI.device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline)); + + // We can destroy shader module now since it will no longer be used. + vkAPI.vkDestroyShaderModule(vkAPI.device, vkShaderModule, nullptr); + + return 0; +} -// Now that we've covered the function that actually loads and -// compiles our Slang shade code, we can go through the rest -// of the application code without as much commentary. -// -Slang::Result initialize() +int HelloWorldExample::createInOutBuffers() { - // Create a window for our application to render into. - // - initializeBase("hello-world", 1024, 768); - - // We will create objects needed to configur the "input assembler" - // (IA) stage of the D3D pipeline. - // - // First, we create an input layout: - // - InputElementDesc inputElements[] = { - { "POSITION", 0, Format::RGB_Float32, offsetof(Vertex, position) }, - { "COLOR", 0, Format::RGB_Float32, offsetof(Vertex, color) }, - }; - auto inputLayout = gDevice->createInputLayout( - &inputElements[0], - 2); - if(!inputLayout) return SLANG_FAIL; - - // Next we allocate a vertex buffer for our pre-initialized - // vertex data. - // - IBufferResource::Desc vertexBufferDesc; - vertexBufferDesc.init(kVertexCount * sizeof(Vertex)); - vertexBufferDesc.setDefaults(IResource::Usage::VertexBuffer); - gVertexBuffer = gDevice->createBufferResource( - IResource::Usage::VertexBuffer, - vertexBufferDesc, - &kVertexData[0]); - if(!gVertexBuffer) return SLANG_FAIL; - - // Now we will use our `loadShaderProgram` function to load - // the code from `shaders.slang` into the graphics API. - // - ComPtr shaderProgram; - SLANG_RETURN_ON_FAIL(loadShaderProgram(gDevice, shaderProgram.writeRef())); - - // Following the D3D12/Vulkan style of API, we need a pipeline state object - // (PSO) to encapsulate the configuration of the overall graphics pipeline. - // - GraphicsPipelineStateDesc desc; - desc.inputLayout = inputLayout; - desc.program = shaderProgram; - desc.framebufferLayout = gFramebufferLayout; - auto pipelineState = gDevice->createGraphicsPipelineState(desc); - if (!pipelineState) - return SLANG_FAIL; + // Create input and output buffers that resides in device-local memory. + for (int i = 0; i < 3; i++) + { + VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferCreateInfo.size = bufferSize; + bufferCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT; + RETURN_ON_FAIL( + vkAPI.vkCreateBuffer(vkAPI.device, &bufferCreateInfo, nullptr, &inOutBuffers[i])); + VkMemoryRequirements memoryReqs = {}; + vkAPI.vkGetBufferMemoryRequirements(vkAPI.device, inOutBuffers[i], &memoryReqs); + + int memoryTypeIndex = vkAPI.findMemoryTypeIndex( + memoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + assert(memoryTypeIndex >= 0); + + VkMemoryPropertyFlags actualMemoryProperites = + vkAPI.deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags; + + VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocateInfo.allocationSize = memoryReqs.size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + RETURN_ON_FAIL( + vkAPI.vkAllocateMemory(vkAPI.device, &allocateInfo, nullptr, &bufferMemories[i])); + RETURN_ON_FAIL( + vkAPI.vkBindBufferMemory(vkAPI.device, inOutBuffers[i], bufferMemories[i], 0)); + } - gPipelineState = pipelineState; + // Create the device memory and buffer object used for reading/writing + // data to/from the device local buffers. + { + VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferCreateInfo.size = bufferSize; + bufferCreateInfo.usage = + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + RETURN_ON_FAIL( + vkAPI.vkCreateBuffer(vkAPI.device, &bufferCreateInfo, nullptr, &stagingBuffer)); + VkMemoryRequirements memoryReqs = {}; + vkAPI.vkGetBufferMemoryRequirements(vkAPI.device, stagingBuffer, &memoryReqs); + + int memoryTypeIndex = vkAPI.findMemoryTypeIndex( + memoryReqs.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + assert(memoryTypeIndex >= 0); + + VkMemoryPropertyFlags actualMemoryProperites = + vkAPI.deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags; + + VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocateInfo.allocationSize = memoryReqs.size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + RETURN_ON_FAIL( + vkAPI.vkAllocateMemory(vkAPI.device, &allocateInfo, nullptr, &stagingMemory)); + RETURN_ON_FAIL(vkAPI.vkBindBufferMemory(vkAPI.device, stagingBuffer, stagingMemory, 0)); + } - return SLANG_OK; + // Map staging buffer and writes in the initial input content. + float* stagingBufferData = nullptr; + vkAPI.vkMapMemory(vkAPI.device, stagingMemory, 0, bufferSize, 0, (void**)&stagingBufferData); + if (!stagingBufferData) + return -1; + for (size_t i = 0; i < inputElementCount; i++) + stagingBufferData[i] = static_cast(i); + vkAPI.vkUnmapMemory(vkAPI.device, stagingMemory); + + // Create a temporary command buffer for recording commands that writes initial + // data into the input buffers. + VkCommandBuffer uploadCommandBuffer; + VkCommandBufferAllocateInfo commandBufferAllocInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + commandBufferAllocInfo.commandBufferCount = 1; + commandBufferAllocInfo.commandPool = commandPool; + commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + RETURN_ON_FAIL(vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &uploadCommandBuffer)); + + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vkAPI.vkBeginCommandBuffer(uploadCommandBuffer, &beginInfo); + VkBufferCopy bufferCopy = {}; + bufferCopy.size = bufferSize; + vkAPI.vkCmdCopyBuffer(uploadCommandBuffer, stagingBuffer, inOutBuffers[0], 1, &bufferCopy); + vkAPI.vkCmdCopyBuffer(uploadCommandBuffer, stagingBuffer, inOutBuffers[1], 1, &bufferCopy); + vkAPI.vkEndCommandBuffer(uploadCommandBuffer); + VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &uploadCommandBuffer; + vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + vkAPI.vkQueueWaitIdle(queue); + vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &uploadCommandBuffer); + return 0; } -// With the initialization out of the way, we can now turn our attention -// to the per-frame rendering logic. As with the initialization, there is -// nothing really Slang-specific here, so the commentary doesn't need -// to be very detailed. -// -virtual void renderFrame(int frameBufferIndex) override +int HelloWorldExample::dispatchCompute() { - ComPtr commandBuffer = gTransientHeaps[frameBufferIndex]->createCommandBuffer(); - auto renderEncoder = commandBuffer->encodeRenderCommands(gRenderPass, gFramebuffers[frameBufferIndex]); - - gfx::Viewport viewport = {}; - viewport.maxZ = 1.0f; - viewport.extentX = (float)windowWidth; - viewport.extentY = (float)windowHeight; - renderEncoder->setViewportAndScissor(viewport); - - // In order to bind shader parameters to the pipeline, we need - // to know how those parameters were assigned to locations/bindings/registers - // for the target graphics API. - // - // The Slang compiler assigns locations to parameters in a deterministic - // fashion, so it is possible for a programmer to hard-code locations - // into their application code that will match up with their shaders. - // - // Hard-coding of locations can become intractable as an application needs - // to support more different target platforms and graphics APIs, as well - // as more shaders with different specialized variants. - // - // Rather than rely on hard-coded locations, our examples will make use of - // reflection information provided by the Slang compiler (see `programLayout` - // above), and our example graphics API layer will translate that reflection - // information into a layout for a "root shader object." - // - // The root object will store values/bindings for all of the parameters in - // the `IShaderProgram` used to create the pipeline state. At a conceptual - // level we can think of `rootObject` as representing the "global scope" of - // the shader program that was loaded; it has entries for each global shader - // parameter that was declared. - // - // Readers who are familiar with D3D12 or Vulkan might think of this root - // layout as being similar in spirit to a "root signature" or "pipeline layout." - // - // We start parameter binding by binding the pipeline state in command encoder. - // This method will return a transient root shader object for us to write our - // shader parameters into. - // - auto rootObject = renderEncoder->bindPipeline(gPipelineState); - - // We will update the model-view-projection matrix that is passed - // into the shader code via the `Uniforms` buffer on a per-frame - // basis, even though the data that is loaded does not change - // per-frame (we always use an identity matrix). - // - auto deviceInfo = gDevice->getDeviceInfo(); - - // We know that `rootObject` is a root shader object created - // from our program, and that it is set up to hold values for - // all the parameter of that program. In order to actually - // set values, we need to be able to look up the location - // of speciic parameter that we want to set. - // - // Our example graphics API layer supports this operation - // with the idea of a *shader cursor* which can be thought - // of as pointing "into" a particular shader object at - // some location/offset. This design choice abstracts over - // the many ways that different platforms and APIs represent - // the necessary offset information. - // - // We construct an initial shader cursor that points at the - // entire shader program. You can think of this as akin to - // a diretory path of `/` for the root directory in a file - // system. - // - ShaderCursor rootCursor(rootObject); - // - // Next, we use a convenience overload of `operator[]` to - // navigate from the root cursor down to the parameter we - // want to set. - // - // The operation `rootCursor["Uniforms"]` looks up the - // offset/location of the global shader parameter `Uniforms` - // (which is a uniform/constant buffer), and the subsequent - // `["modelViewProjection"]` step navigates from there down - // to the member named `modelViewProjection` in that buffer. - // - // Once we have formed a cursor that "points" at the - // model-view projection matrix, we can set its data directly. - // - rootCursor["Uniforms"]["modelViewProjection"].setData( - deviceInfo.identityProjectionMatrix, sizeof(float) * 16); - // - // Some readers might be concerned about the performance o - // the above operations because of the use of strings. For - // those readers, here are two things to note: - // - // * While these `operator[]` steps do need to perform string - // comparisons, they do *not* make copies of the strings or - // perform any heap allocation. - // - // * There are other overloads of `operator[]` that use the - // *index* of a parameter/field instead of its name, and those - // operations have fixed/constant overhead and perform no - // string comparisons. The indices used are independent of - // the target platform and graphics API, and can thus be - // hard-coded even in cross-platform code. - // - - // We also need to set up a few pieces of fixed-function pipeline - // state that are not bound by the pipeline state above. - // - renderEncoder->setVertexBuffer(0, gVertexBuffer, sizeof(Vertex)); - renderEncoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); - - // Finally, we are ready to issue a draw call for a single triangle. - // - renderEncoder->draw(3); - renderEncoder->endEncoding(); - commandBuffer->close(); - gQueue->executeCommandBuffer(commandBuffer); - - // With that, we are done drawing for one frame, and ready for the next. - // - gSwapchain->present(); + // Create a descriptor pool. + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + VkDescriptorPoolSize poolSizes[] = { + VkDescriptorPoolSize{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}}; + descriptorPoolCreateInfo.maxSets = 4; + descriptorPoolCreateInfo.poolSizeCount = sizeof(poolSizes) / sizeof(VkDescriptorPoolSize); + descriptorPoolCreateInfo.pPoolSizes = poolSizes; + descriptorPoolCreateInfo.flags = 0; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + RETURN_ON_FAIL(vkAPI.vkCreateDescriptorPool( + vkAPI.device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); + + // Allocate descriptor set. + VkDescriptorSetAllocateInfo descSetAllocInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + descSetAllocInfo.descriptorPool = descriptorPool; + descSetAllocInfo.descriptorSetCount = 1; + descSetAllocInfo.pSetLayouts = &descriptorSetLayout; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + RETURN_ON_FAIL(vkAPI.vkAllocateDescriptorSets(vkAPI.device, &descSetAllocInfo, &descriptorSet)); + + // Write descriptor set. + VkWriteDescriptorSet descriptorSetWrites[3] = {}; + VkDescriptorBufferInfo bufferInfo[3]; + for (int i = 0; i < 3; i++) + { + bufferInfo[i].buffer = inOutBuffers[i]; + bufferInfo[i].offset = 0; + bufferInfo[i].range = bufferSize; + + descriptorSetWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorSetWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorSetWrites[i].descriptorCount = 1; + descriptorSetWrites[i].dstBinding = i; + descriptorSetWrites[i].dstSet = descriptorSet; + descriptorSetWrites[i].pBufferInfo = &bufferInfo[i]; + } + vkAPI.vkUpdateDescriptorSets(vkAPI.device, 3, descriptorSetWrites, 0, nullptr); + + // Allocate command buffer and record dispatch commands. + VkCommandBuffer commandBuffer; + VkCommandBufferAllocateInfo commandBufferAllocInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + commandBufferAllocInfo.commandBufferCount = 1; + commandBufferAllocInfo.commandPool = commandPool; + commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + RETURN_ON_FAIL( + vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &commandBuffer)); + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vkAPI.vkBeginCommandBuffer(commandBuffer, &beginInfo); + vkAPI.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + vkAPI.vkCmdBindDescriptorSets( + commandBuffer, + VK_PIPELINE_BIND_POINT_COMPUTE, + pipelineLayout, + 0, + 1, + &descriptorSet, + 0, + nullptr); + vkAPI.vkCmdDispatch(commandBuffer, (uint32_t)inputElementCount, 1, 1); + vkAPI.vkEndCommandBuffer(commandBuffer); + + // Submit command buffer and wait. + VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + vkAPI.vkQueueWaitIdle(queue); + vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &commandBuffer); + + // Clean up. + vkAPI.vkDestroyDescriptorPool(vkAPI.device, descriptorPool, nullptr); + return 0; } -}; +int HelloWorldExample::printComputeResults() +{ + // Allocate command buffer to read back data. + VkCommandBuffer commandBuffer; + VkCommandBufferAllocateInfo commandBufferAllocInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + commandBufferAllocInfo.commandBufferCount = 1; + commandBufferAllocInfo.commandPool = commandPool; + commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + RETURN_ON_FAIL( + vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &commandBuffer)); + + // Record commands to copy output buffer into staging buffer. + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vkAPI.vkBeginCommandBuffer(commandBuffer, &beginInfo); + VkBufferCopy bufferCopy = {}; + bufferCopy.size = bufferSize; + vkAPI.vkCmdCopyBuffer(commandBuffer, inOutBuffers[2], stagingBuffer, 1, &bufferCopy); + vkAPI.vkEndCommandBuffer(commandBuffer); + + // Execute command buffer and wait. + VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + vkAPI.vkQueueWaitIdle(queue); + vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &commandBuffer); + + // Map and read back staging buffer. + float* stagingBufferData = nullptr; + vkAPI.vkMapMemory(vkAPI.device, stagingMemory, 0, bufferSize, 0, (void**)&stagingBufferData); + if (!stagingBufferData) + return -1; + for (size_t i = 0; i < inputElementCount; i++) + { + printf("%f\n", stagingBufferData[i]); + } + return 0; +} -// This macro instantiates an appropriate main function to -// run the application defined above. -PLATFORM_UI_MAIN(innerMain) +HelloWorldExample::~HelloWorldExample() +{ + vkAPI.vkDestroyPipeline(vkAPI.device, pipeline, nullptr); + for (int i = 0; i < 3; i++) + { + vkAPI.vkDestroyBuffer(vkAPI.device, inOutBuffers[i], nullptr); + vkAPI.vkFreeMemory(vkAPI.device, bufferMemories[i], nullptr); + } + vkAPI.vkDestroyBuffer(vkAPI.device, stagingBuffer, nullptr); + vkAPI.vkFreeMemory(vkAPI.device, stagingMemory, nullptr); + vkAPI.vkDestroyPipelineLayout(vkAPI.device, pipelineLayout, nullptr); + vkAPI.vkDestroyDescriptorSetLayout(vkAPI.device, descriptorSetLayout, nullptr); + vkAPI.vkDestroyCommandPool(vkAPI.device, commandPool, nullptr); +} diff --git a/examples/hello-world/shaders.slang b/examples/hello-world/shaders.slang deleted file mode 100644 index fe35db9c8..000000000 --- a/examples/hello-world/shaders.slang +++ /dev/null @@ -1,66 +0,0 @@ -// shaders.slang - -// -// This file provides a simple vertex and fragment shader that can be compiled -// using Slang. This code should also be valid as HLSL, and thus it does not -// use any of the new language features supported by Slang. -// - -// Uniform data to be passed from application -> shader. -cbuffer Uniforms -{ - float4x4 modelViewProjection; -} - -// Per-vertex attributes to be assembled from bound vertex buffers. -struct AssembledVertex -{ - float3 position : POSITION; - float3 color : COLOR; -}; - -// Output of the vertex shader, and input to the fragment shader. -struct CoarseVertex -{ - float3 color; -}; - -// Output of the fragment shader -struct Fragment -{ - float4 color; -}; - -// Vertex Shader - -struct VertexStageOutput -{ - CoarseVertex coarseVertex : CoarseVertex; - float4 sv_position : SV_Position; -}; - -[shader("vertex")] -VertexStageOutput vertexMain( - AssembledVertex assembledVertex) -{ - VertexStageOutput output; - - float3 position = assembledVertex.position; - float3 color = assembledVertex.color; - - output.coarseVertex.color = color; - output.sv_position = mul(modelViewProjection, float4(position, 1.0)); - - return output; -} - -// Fragment Shader - -[shader("fragment")] -float4 fragmentMain( - CoarseVertex coarseVertex : CoarseVertex) : SV_Target -{ - float3 color = coarseVertex.color; - - return float4(color, 1.0); -} diff --git a/examples/hello-world/vulkan-api.cpp b/examples/hello-world/vulkan-api.cpp new file mode 100644 index 000000000..529ca7196 --- /dev/null +++ b/examples/hello-world/vulkan-api.cpp @@ -0,0 +1,228 @@ +#include "vulkan-api.h" +#include "slang.h" + +#include +#include +#include +#include + +#if SLANG_WINDOWS_FAMILY +# include +#else +# include +#endif + +#if _DEBUG +#define ENABLE_VALIDATION_LAYER 1 +#endif + +VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback( + VkDebugReportFlagsEXT /*flags*/, + VkDebugReportObjectTypeEXT /*objType*/, + uint64_t /*srcObject*/, + size_t /*location*/, + int32_t /*msgCode*/, + const char* pLayerPrefix, + const char* pMsg, + void* /*pUserData*/) +{ + printf("[%s]: %s\n", pLayerPrefix, pMsg); + return 1; +} + +int initializeVulkanDevice(VulkanAPI& api) +{ + // Load vulkan library. + const char* dynamicLibraryName = "Unknown"; + +#if SLANG_WINDOWS_FAMILY + dynamicLibraryName = "vulkan-1.dll"; + HMODULE module = ::LoadLibraryA(dynamicLibraryName); + api.vulkanLibraryHandle = (void*)module; +#define VK_API_GET_GLOBAL_PROC(x) api.x = (PFN_##x)GetProcAddress(module, #x); +#else + dynamicLibraryName = "libvulkan.so.1"; + api.vulkanLibraryHandle = dlopen(dynamicLibraryName, RTLD_NOW); +#define VK_API_GET_GLOBAL_PROC(x) api.x = (PFN_##x)dlsym(api.vulkanLibraryHandle, #x); +#endif + + // Initialize all the global functions. + VK_API_ALL_GLOBAL_PROCS(VK_API_GET_GLOBAL_PROC) + if (!api.vkCreateInstance) + return -1; + + // Create Vulkan Instance. + VkApplicationInfo applicationInfo = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; + applicationInfo.pApplicationName = "slang-hello-world"; + applicationInfo.pEngineName = "slang-hello-world"; + applicationInfo.apiVersion = VK_API_VERSION_1_0; + applicationInfo.engineVersion = 1; + applicationInfo.applicationVersion = 1; + const char* instanceExtensions[] = { + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + VK_EXT_DEBUG_REPORT_EXTENSION_NAME, + }; + VkInstanceCreateInfo instanceCreateInfo = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; + instanceCreateInfo.pApplicationInfo = &applicationInfo; + instanceCreateInfo.enabledExtensionCount = SLANG_COUNT_OF(instanceExtensions); + instanceCreateInfo.ppEnabledExtensionNames = &instanceExtensions[0]; + std::vector layers; +#ifdef ENABLE_VALIDATION_LAYER + layers.push_back("VK_LAYER_KHRONOS_validation"); +#endif + if (layers.size()) + { + instanceCreateInfo.ppEnabledLayerNames = &layers[0]; + instanceCreateInfo.enabledLayerCount = (uint32_t)layers.size(); + } + if (api.vkCreateInstance(&instanceCreateInfo, nullptr, &api.instance) != 0) + return -1; + + // Load instance functions. + api.initInstanceProcs(); + + // Create debug report callback. + if (api.vkCreateDebugReportCallbackEXT) + { + VkDebugReportFlagsEXT debugFlags = + VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; + + VkDebugReportCallbackCreateInfoEXT debugCreateInfo = { + VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT}; + debugCreateInfo.pfnCallback = &debugMessageCallback; + debugCreateInfo.pUserData = nullptr; + debugCreateInfo.flags = debugFlags; + + RETURN_ON_FAIL(api.vkCreateDebugReportCallbackEXT( + api.instance, &debugCreateInfo, nullptr, &api.debugReportCallback)); + } + + // Enumerate physical devices. + uint32_t numPhysicalDevices = 0; + RETURN_ON_FAIL( + api.vkEnumeratePhysicalDevices(api.instance, &numPhysicalDevices, nullptr)); + std::vector physicalDevices; + physicalDevices.resize(numPhysicalDevices); + RETURN_ON_FAIL(api.vkEnumeratePhysicalDevices( + api.instance, &numPhysicalDevices, &physicalDevices[0])); + + // We will use device 0. + api.initPhysicalDevice(physicalDevices[0]); + + VkDeviceCreateInfo deviceCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; + deviceCreateInfo.queueCreateInfoCount = 1; + deviceCreateInfo.pEnabledFeatures = &api.deviceFeatures; + + // Find proper queue family index. + uint32_t numQueueFamilies = 0; + api.vkGetPhysicalDeviceQueueFamilyProperties(api.physicalDevice, &numQueueFamilies, nullptr); + + std::vector queueFamilies; + queueFamilies.resize(numQueueFamilies); + api.vkGetPhysicalDeviceQueueFamilyProperties( + api.physicalDevice, &numQueueFamilies, &queueFamilies[0]); + + // Find a queue that can service our needs. + auto requiredQueueFlags = VK_QUEUE_COMPUTE_BIT; + for (int i = 0; i < int(numQueueFamilies); ++i) + { + if ((queueFamilies[i].queueFlags & requiredQueueFlags) == requiredQueueFlags) + { + api.queueFamilyIndex = i; + break; + } + } + if (api.queueFamilyIndex == -1) + return -1; + + VkDeviceQueueCreateInfo queueCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + float queuePriority = 0.0f; + queueCreateInfo.queueFamilyIndex = api.queueFamilyIndex; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + deviceCreateInfo.enabledExtensionCount = 0; + deviceCreateInfo.ppEnabledExtensionNames = nullptr; + RETURN_ON_FAIL(api.vkCreateDevice(api.physicalDevice, &deviceCreateInfo, nullptr, &api.device)); + + // Load device functions. + api.initDeviceProcs(); + + return 0; +} + +int VulkanAPI::initInstanceProcs() +{ + assert(instance && vkGetInstanceProcAddr != nullptr); + +#define VK_API_GET_INSTANCE_PROC(x) x = (PFN_##x)vkGetInstanceProcAddr(instance, #x); + + VK_API_ALL_INSTANCE_PROCS(VK_API_GET_INSTANCE_PROC) + // Get optional + VK_API_INSTANCE_PROCS_OPT(VK_API_GET_INSTANCE_PROC) + +#undef VK_API_GET_INSTANCE_PROC + + return 0; +} + +int VulkanAPI::initPhysicalDevice(VkPhysicalDevice inPhysicalDevice) +{ + assert(physicalDevice == VK_NULL_HANDLE); + physicalDevice = inPhysicalDevice; + + vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); + vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures); + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); + + return 0; +} + +int VulkanAPI::initDeviceProcs() +{ + assert(instance && device && vkGetDeviceProcAddr != nullptr); + +#define VK_API_GET_DEVICE_PROC(x) x = (PFN_##x)vkGetDeviceProcAddr(device, #x); + VK_API_DEVICE_PROCS(VK_API_GET_DEVICE_PROC) +#undef VK_API_GET_DEVICE_PROC + + return 0; +} + +int VulkanAPI::findMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) +{ + assert(typeBits); + + const int numMemoryTypes = int(deviceMemoryProperties.memoryTypeCount); + + // bit holds current test bit against typeBits. Ie bit == 1 << typeBits + + uint32_t bit = 1; + for (int i = 0; i < numMemoryTypes; ++i, bit += bit) + { + auto const& memoryType = deviceMemoryProperties.memoryTypes[i]; + if ((typeBits & bit) && (memoryType.propertyFlags & properties) == properties) + { + return i; + } + } + + // assert(!"failed to find a usable memory type"); + return -1; +} + +VulkanAPI::~VulkanAPI() +{ + if (vkDestroyDevice) + { + vkDestroyDevice(device, nullptr); + } + if (vkDestroyDebugReportCallbackEXT) + { + vkDestroyDebugReportCallbackEXT(instance, debugReportCallback, nullptr); + } + if (vkDestroyInstance) + { + vkDestroyInstance(instance, nullptr); + } +} diff --git a/examples/hello-world/vulkan-api.h b/examples/hello-world/vulkan-api.h new file mode 100644 index 000000000..17d0465a6 --- /dev/null +++ b/examples/hello-world/vulkan-api.h @@ -0,0 +1,132 @@ +#pragma once + +#define VK_NO_PROTOTYPES +#include "vulkan/vulkan.h" + +// This file provides basic loading and helper functions for using +// the Vulkan API. + +// The Vulkan function pointers we will use in this example. +#define VK_API_GLOBAL_PROCS(x) \ + x(vkGetInstanceProcAddr) \ + x(vkCreateInstance) \ + x(vkEnumerateInstanceLayerProperties) \ + x(vkDestroyInstance) \ + /* */ + +#define VK_API_INSTANCE_PROCS_OPT(x) \ + x(vkGetPhysicalDeviceFeatures2) \ + x(vkGetPhysicalDeviceProperties2) \ + x(vkCreateDebugReportCallbackEXT) \ + x(vkDestroyDebugReportCallbackEXT) \ + x(vkDebugReportMessageEXT) \ + /* */ + +#define VK_API_INSTANCE_PROCS(x) \ + x(vkCreateDevice) \ + x(vkDestroyDevice) \ + x(vkEnumeratePhysicalDevices) \ + x(vkGetPhysicalDeviceProperties) \ + x(vkGetPhysicalDeviceFeatures) \ + x(vkGetPhysicalDeviceMemoryProperties) \ + x(vkGetPhysicalDeviceQueueFamilyProperties) \ + x(vkGetPhysicalDeviceFormatProperties) \ + x(vkGetDeviceProcAddr) \ + /* */ + +#define VK_API_DEVICE_PROCS(x) \ + x(vkCreateDescriptorPool) \ + x(vkDestroyDescriptorPool) \ + x(vkGetDeviceQueue) \ + x(vkQueueSubmit) \ + x(vkQueueWaitIdle) \ + x(vkCreateBuffer) \ + x(vkAllocateMemory) \ + x(vkMapMemory) \ + x(vkUnmapMemory) \ + x(vkCmdCopyBuffer) \ + x(vkDestroyBuffer) \ + x(vkFreeMemory) \ + x(vkCreateDescriptorSetLayout) \ + x(vkDestroyDescriptorSetLayout) \ + x(vkAllocateDescriptorSets) \ + x(vkUpdateDescriptorSets) \ + x(vkCreatePipelineLayout) \ + x(vkDestroyPipelineLayout) \ + x(vkCreateComputePipelines) \ + x(vkDestroyPipeline) \ + x(vkCreateShaderModule) \ + x(vkDestroyShaderModule) \ + x(vkCreateCommandPool) \ + x(vkDestroyCommandPool) \ + \ + x(vkGetBufferMemoryRequirements) \ + \ + x(vkCmdBindPipeline) \ + x(vkCmdBindDescriptorSets) \ + x(vkCmdDispatch) \ + \ + x(vkFreeCommandBuffers) \ + x(vkAllocateCommandBuffers) \ + x(vkBeginCommandBuffer) \ + x(vkEndCommandBuffer) \ + x(vkBindBufferMemory) \ + /* */ + +#define VK_API_ALL_GLOBAL_PROCS(x) \ + VK_API_GLOBAL_PROCS(x) + +#define VK_API_ALL_INSTANCE_PROCS(x) \ + VK_API_INSTANCE_PROCS(x) \ + +#define VK_API_ALL_PROCS(x) \ + VK_API_ALL_GLOBAL_PROCS(x) \ + VK_API_ALL_INSTANCE_PROCS(x) \ + VK_API_DEVICE_PROCS(x) \ + VK_API_INSTANCE_PROCS_OPT(x) \ + /* */ + +#define VK_API_DECLARE_PROC(NAME) PFN_##NAME NAME = nullptr; + +struct VulkanAPI +{ + VkInstance instance = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDebugReportCallbackEXT debugReportCallback = VK_NULL_HANDLE; + void* vulkanLibraryHandle = nullptr; + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + VkPhysicalDeviceMemoryProperties deviceMemoryProperties; + int queueFamilyIndex = -1; + + VK_API_ALL_PROCS(VK_API_DECLARE_PROC) + + /// Initialize the instance functions + int initInstanceProcs(); + + /// Called before initDevice + int initPhysicalDevice(VkPhysicalDevice physicalDevice); + + /// Initialize the device functions + int initDeviceProcs(); + + /// Finds the proper memory type index for a given requirement. + int findMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties); + + /// Clean up and frees device and instance. + ~VulkanAPI(); +}; + +#define RETURN_ON_FAIL(x) \ + { \ + auto _res = x; \ + if (_res != 0) \ + { \ + return -1; \ + } \ + } + +// Loads Vulkan library and creates a VkDevice. +// Returns 0 if successful. +int initializeVulkanDevice(VulkanAPI& api); diff --git a/examples/triangle/README.md b/examples/triangle/README.md new file mode 100644 index 000000000..ba377b8cb --- /dev/null +++ b/examples/triangle/README.md @@ -0,0 +1,12 @@ +Slang "Hello World" Example +=========================== + +The goal of this example is to demonstrate an almost minimal application that uses Slang for shading. + +The `shaders.slang` file contains simple vertex and fragment shader entry points. The shader code should compile as either Slang or HLSL code (that is, this example does not show off any new Slang language features). + +The `main.cpp` file contains the C++ application code, showing how to use the Slang API to load and compile the shader code to DirectX shader bytecode (DXBC). +The application perform rendering using the D3D11 API, through a platform and graphics API abstraction layer that is implemented in `tools/gfx`. +Note that this abstraction layer is *not* required in order to work with Slang, and it is just there to help us write example and test applications more conveniently. + +This example is not necessarily representative of best practices for integrating Slang into a production engine; the goal is merely to use the minimum amount of code possible to demonstrate a complete applicaiton that uses Slang. diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp new file mode 100644 index 000000000..6b9104072 --- /dev/null +++ b/examples/triangle/main.cpp @@ -0,0 +1,398 @@ +// main.cpp + +// This file implements an extremely simple example of loading and +// executing a Slang shader program. This is primarily an example +// of how to use Slang as a "drop-in" replacement for an existing +// HLSL compiler like the `D3DCompile` API. More advanced usage +// of advanced Slang language and API features is left to the +// next example. +// +// The comments in the file will attempt to explain concepts as +// they are introduced. +// +// Of course, in order to use the Slang API, we need to include +// its header. We have set up the build options for this project +// so that it is as simple as: +// +#include +// +// Other build setups are possible, and Slang doesn't assume that +// its include directory must be added to your global include +// path. + +// For the purposes of keeping the demo code as simple as possible, +// while still retaining some level of portability, our examples +// make use of a small platform and graphics API abstraction layer, +// which is included in the Slang source distribution under the +// `tools/` directory. +// +// Applications can of course use Slang without ever touching this +// abstraction layer, so we will not focus on it when explaining +// examples, except in places where best practices for interacting +// with Slang may depend on an application/engine making certain +// design choices in their abstraction layer. +// +#include "slang-gfx.h" +#include "gfx-util/shader-cursor.h" +#include "tools/platform/window.h" +#include "slang-com-ptr.h" +#include "source/core/slang-basic.h" +#include "examples/example-base/example-base.h" + +using namespace gfx; +using namespace Slang; + +// For the purposes of a small example, we will define the vertex data for a +// single triangle directly in the source file. It should be easy to extend +// this example to load data from an external source, if desired. +// +struct Vertex +{ + float position[3]; + float color[3]; +}; + +static const int kVertexCount = 3; +static const Vertex kVertexData[kVertexCount] = +{ + { { 0, 0, 0.5 }, { 1, 0, 0 } }, + { { 0, 1, 0.5 }, { 0, 0, 1 } }, + { { 1, 0, 0.5 }, { 0, 1, 0 } }, +}; + +// The example application will be implemented as a `struct`, so that +// we can scope the resources it allocates without using global variables. +// +struct HelloWorld : public WindowedAppBase +{ + +// 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 task an application cares about is compiling shader code +// from souce (if needed) and loading it through the chosen graphics API. +// +// In addition, an application may want to receive reflection information +// about the program, which is what a `slang::ProgramLayout` provides. +// +gfx::Result loadShaderProgram( + gfx::IDevice* device, + gfx::IShaderProgram** outProgram) +{ + // We need to obatin a compilation session (`slang::ISession`) that will provide + // a scope to all the compilation and loading of code we do. + // + // Our example application uses the `gfx` graphics API abstraction layer, which already + // creates a Slang compilation session for us, so we just grab and use it here. + ComPtr slangSession; + slangSession = device->getSlangSession(); + + // We can now start loading code into the slang session. + // + // The simplest way to load code is by calling `loadModule` with the name of a Slang + // module. A call to `loadModule("MyStuff")` will behave more or less as if you + // wrote: + // + // import MyStuff; + // + // In a Slang shader file. The compiler will use its search paths to try to locate + // `MyModule.slang`, then compile and load that file. If a matching module had + // already been loaded previously, that would be used directly. + // + ComPtr diagnosticsBlob; + slang::IModule* module = slangSession->loadModule("shaders", diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + if(!module) + return SLANG_FAIL; + + // Loading the `shaders` module will compile and check all the shader code in it, + // including the shader entry points we want to use. Now that the module is loaded + // we can look up those entry points by name. + // + // Note: If you are using this `loadModule` approach to load your shader code it is + // important to tag your entry point functions with the `[shader("...")]` attribute + // (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there + // is no umambiguous way for the compiler to know which functions represent entry + // points when it parses your code via `loadModule()`. + // + ComPtr vertexEntryPoint; + SLANG_RETURN_ON_FAIL(module->findEntryPointByName("vertexMain", vertexEntryPoint.writeRef())); + // + ComPtr fragmentEntryPoint; + SLANG_RETURN_ON_FAIL(module->findEntryPointByName("fragmentMain", fragmentEntryPoint.writeRef())); + + // At this point we have a few different Slang API objects that represent + // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. + // + // A single Slang module could contain many different entry points (e.g., + // four vertex entry points, three fragment entry points, and two compute + // shaders), and before we try to generate output code for our target API + // we need to identify which entry points we plan to use together. + // + // Modules and entry points are both examples of *component types* in the + // Slang API. The API also provides a way to build a *composite* out of + // other pieces, and that is what we are going to do with our module + // and entry points. + // + Slang::List componentTypes; + componentTypes.add(module); + + // Later on when we go to extract compiled kernel code for our vertex + // and fragment shaders, we will need to make use of their order within + // the composition, so we will record the relative ordering of the entry + // points here as we add them. + int entryPointCount = 0; + int vertexEntryPointIndex = entryPointCount++; + componentTypes.add(vertexEntryPoint); + + int fragmentEntryPointIndex = entryPointCount++; + componentTypes.add(fragmentEntryPoint); + + // Actually creating the composite component type is a single operation + // on the Slang session, but the operation could potentially fail if + // something about the composite was invalid (e.g., you are trying to + // combine multiple copies of the same module), so we need to deal + // with the possibility of diagnostic output. + // + ComPtr linkedProgram; + SlangResult result = slangSession->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + linkedProgram.writeRef(), + diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + SLANG_RETURN_ON_FAIL(result); + + // Once we've described the particular composition of entry points + // that we want to compile, we defer to the graphics API layer + // to extract compiled kernel code and load it into the API-specific + // program representation. + // + gfx::IShaderProgram::Desc programDesc = {}; + programDesc.pipelineType = gfx::PipelineType::Graphics; + programDesc.slangProgram = linkedProgram; + SLANG_RETURN_ON_FAIL(device->createProgram(programDesc, outProgram)); + + return SLANG_OK; +} + +// +// The above function shows the core of what is required to use the +// Slang API as a simple compiler (e.g., a drop-in replacement for +// fxc or dxc). +// +// The rest of this file implements an extremely simple rendering application +// that will execute the vertex/fragment shaders loaded with the function +// we have just defined. +// + +// We will define global variables for the various platform and +// graphics API objects that our application needs: +// +// As a reminder, *none* of these are Slang API objects. All +// of them come from the utility library we are using to simplify +// building an example program. +// +ComPtr gPipelineState; +ComPtr gVertexBuffer; + +// Now that we've covered the function that actually loads and +// compiles our Slang shade code, we can go through the rest +// of the application code without as much commentary. +// +Slang::Result initialize() +{ + // Create a window for our application to render into. + // + initializeBase("hello-world", 1024, 768); + + // We will create objects needed to configur the "input assembler" + // (IA) stage of the D3D pipeline. + // + // First, we create an input layout: + // + InputElementDesc inputElements[] = { + { "POSITION", 0, Format::RGB_Float32, offsetof(Vertex, position) }, + { "COLOR", 0, Format::RGB_Float32, offsetof(Vertex, color) }, + }; + auto inputLayout = gDevice->createInputLayout( + &inputElements[0], + 2); + if(!inputLayout) return SLANG_FAIL; + + // Next we allocate a vertex buffer for our pre-initialized + // vertex data. + // + IBufferResource::Desc vertexBufferDesc; + vertexBufferDesc.init(kVertexCount * sizeof(Vertex)); + vertexBufferDesc.setDefaults(IResource::Usage::VertexBuffer); + gVertexBuffer = gDevice->createBufferResource( + IResource::Usage::VertexBuffer, + vertexBufferDesc, + &kVertexData[0]); + if(!gVertexBuffer) return SLANG_FAIL; + + // Now we will use our `loadShaderProgram` function to load + // the code from `shaders.slang` into the graphics API. + // + ComPtr shaderProgram; + SLANG_RETURN_ON_FAIL(loadShaderProgram(gDevice, shaderProgram.writeRef())); + + // Following the D3D12/Vulkan style of API, we need a pipeline state object + // (PSO) to encapsulate the configuration of the overall graphics pipeline. + // + GraphicsPipelineStateDesc desc; + desc.inputLayout = inputLayout; + desc.program = shaderProgram; + desc.framebufferLayout = gFramebufferLayout; + auto pipelineState = gDevice->createGraphicsPipelineState(desc); + if (!pipelineState) + return SLANG_FAIL; + + gPipelineState = pipelineState; + + return SLANG_OK; +} + +// With the initialization out of the way, we can now turn our attention +// to the per-frame rendering logic. As with the initialization, there is +// nothing really Slang-specific here, so the commentary doesn't need +// to be very detailed. +// +virtual void renderFrame(int frameBufferIndex) override +{ + ComPtr commandBuffer = gTransientHeaps[frameBufferIndex]->createCommandBuffer(); + auto renderEncoder = commandBuffer->encodeRenderCommands(gRenderPass, gFramebuffers[frameBufferIndex]); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = (float)windowWidth; + viewport.extentY = (float)windowHeight; + renderEncoder->setViewportAndScissor(viewport); + + // In order to bind shader parameters to the pipeline, we need + // to know how those parameters were assigned to locations/bindings/registers + // for the target graphics API. + // + // The Slang compiler assigns locations to parameters in a deterministic + // fashion, so it is possible for a programmer to hard-code locations + // into their application code that will match up with their shaders. + // + // Hard-coding of locations can become intractable as an application needs + // to support more different target platforms and graphics APIs, as well + // as more shaders with different specialized variants. + // + // Rather than rely on hard-coded locations, our examples will make use of + // reflection information provided by the Slang compiler (see `programLayout` + // above), and our example graphics API layer will translate that reflection + // information into a layout for a "root shader object." + // + // The root object will store values/bindings for all of the parameters in + // the `IShaderProgram` used to create the pipeline state. At a conceptual + // level we can think of `rootObject` as representing the "global scope" of + // the shader program that was loaded; it has entries for each global shader + // parameter that was declared. + // + // Readers who are familiar with D3D12 or Vulkan might think of this root + // layout as being similar in spirit to a "root signature" or "pipeline layout." + // + // We start parameter binding by binding the pipeline state in command encoder. + // This method will return a transient root shader object for us to write our + // shader parameters into. + // + auto rootObject = renderEncoder->bindPipeline(gPipelineState); + + // We will update the model-view-projection matrix that is passed + // into the shader code via the `Uniforms` buffer on a per-frame + // basis, even though the data that is loaded does not change + // per-frame (we always use an identity matrix). + // + auto deviceInfo = gDevice->getDeviceInfo(); + + // We know that `rootObject` is a root shader object created + // from our program, and that it is set up to hold values for + // all the parameter of that program. In order to actually + // set values, we need to be able to look up the location + // of speciic parameter that we want to set. + // + // Our example graphics API layer supports this operation + // with the idea of a *shader cursor* which can be thought + // of as pointing "into" a particular shader object at + // some location/offset. This design choice abstracts over + // the many ways that different platforms and APIs represent + // the necessary offset information. + // + // We construct an initial shader cursor that points at the + // entire shader program. You can think of this as akin to + // a diretory path of `/` for the root directory in a file + // system. + // + ShaderCursor rootCursor(rootObject); + // + // Next, we use a convenience overload of `operator[]` to + // navigate from the root cursor down to the parameter we + // want to set. + // + // The operation `rootCursor["Uniforms"]` looks up the + // offset/location of the global shader parameter `Uniforms` + // (which is a uniform/constant buffer), and the subsequent + // `["modelViewProjection"]` step navigates from there down + // to the member named `modelViewProjection` in that buffer. + // + // Once we have formed a cursor that "points" at the + // model-view projection matrix, we can set its data directly. + // + rootCursor["Uniforms"]["modelViewProjection"].setData( + deviceInfo.identityProjectionMatrix, sizeof(float) * 16); + // + // Some readers might be concerned about the performance o + // the above operations because of the use of strings. For + // those readers, here are two things to note: + // + // * While these `operator[]` steps do need to perform string + // comparisons, they do *not* make copies of the strings or + // perform any heap allocation. + // + // * There are other overloads of `operator[]` that use the + // *index* of a parameter/field instead of its name, and those + // operations have fixed/constant overhead and perform no + // string comparisons. The indices used are independent of + // the target platform and graphics API, and can thus be + // hard-coded even in cross-platform code. + // + + // We also need to set up a few pieces of fixed-function pipeline + // state that are not bound by the pipeline state above. + // + renderEncoder->setVertexBuffer(0, gVertexBuffer, sizeof(Vertex)); + renderEncoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + // Finally, we are ready to issue a draw call for a single triangle. + // + renderEncoder->draw(3); + renderEncoder->endEncoding(); + commandBuffer->close(); + gQueue->executeCommandBuffer(commandBuffer); + + // With that, we are done drawing for one frame, and ready for the next. + // + gSwapchain->present(); +} + +}; + +// This macro instantiates an appropriate main function to +// run the application defined above. +PLATFORM_UI_MAIN(innerMain) diff --git a/examples/triangle/shaders.slang b/examples/triangle/shaders.slang new file mode 100644 index 000000000..fe35db9c8 --- /dev/null +++ b/examples/triangle/shaders.slang @@ -0,0 +1,66 @@ +// shaders.slang + +// +// This file provides a simple vertex and fragment shader that can be compiled +// using Slang. This code should also be valid as HLSL, and thus it does not +// use any of the new language features supported by Slang. +// + +// Uniform data to be passed from application -> shader. +cbuffer Uniforms +{ + float4x4 modelViewProjection; +} + +// Per-vertex attributes to be assembled from bound vertex buffers. +struct AssembledVertex +{ + float3 position : POSITION; + float3 color : COLOR; +}; + +// Output of the vertex shader, and input to the fragment shader. +struct CoarseVertex +{ + float3 color; +}; + +// Output of the fragment shader +struct Fragment +{ + float4 color; +}; + +// Vertex Shader + +struct VertexStageOutput +{ + CoarseVertex coarseVertex : CoarseVertex; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexStageOutput vertexMain( + AssembledVertex assembledVertex) +{ + VertexStageOutput output; + + float3 position = assembledVertex.position; + float3 color = assembledVertex.color; + + output.coarseVertex.color = color; + output.sv_position = mul(modelViewProjection, float4(position, 1.0)); + + return output; +} + +// Fragment Shader + +[shader("fragment")] +float4 fragmentMain( + CoarseVertex coarseVertex : CoarseVertex) : SV_Target +{ + float3 color = coarseVertex.color; + + return float4(color, 1.0); +} diff --git a/premake5.lua b/premake5.lua index ef53ed47a..c191f5ec6 100644 --- a/premake5.lua +++ b/premake5.lua @@ -634,6 +634,8 @@ end -- declaration of the "Hello, World" example project: -- example "hello-world" + kind "ConsoleApp" + includedirs {"external/vulkan/include"} -- -- Note how we are calling our custom `example()` subroutine with -- the same syntax sugar that Premake usually advocates for their @@ -642,6 +644,8 @@ example "hello-world" -- -- Let's go ahead and set up the projects for our other example now. +example "triangle" + example "gpu-printing" kind "ConsoleApp" diff --git a/slang.sln b/slang.sln index 75bbf8097..682eeea30 100644 --- a/slang.sln +++ b/slang.sln @@ -21,6 +21,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shader-object", "build\visu EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shader-toy", "build\visual-studio\shader-toy\shader-toy.vcxproj", "{0FC5DE93-FBEA-A8FA-E430-2EC6D0F5CDC6}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "triangle", "build\visual-studio\triangle\triangle.vcxproj", "{3BB99068-27C9-3C39-9082-A1577CB12BD2}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generator", "generator", "{F3AB4ED5-5F37-BC99-6848-3F8ED452189A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "run-generators", "build\visual-studio\run-generators\run-generators.vcxproj", "{E145B2B8-CD13-A6BE-B6A7-16E5A2148223}" @@ -143,6 +145,14 @@ Global {0FC5DE93-FBEA-A8FA-E430-2EC6D0F5CDC6}.Release|Win32.Build.0 = Release|Win32 {0FC5DE93-FBEA-A8FA-E430-2EC6D0F5CDC6}.Release|x64.ActiveCfg = Release|x64 {0FC5DE93-FBEA-A8FA-E430-2EC6D0F5CDC6}.Release|x64.Build.0 = Release|x64 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Debug|Win32.ActiveCfg = Debug|Win32 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Debug|Win32.Build.0 = Debug|Win32 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Debug|x64.ActiveCfg = Debug|x64 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Debug|x64.Build.0 = Debug|x64 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Release|Win32.ActiveCfg = Release|Win32 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Release|Win32.Build.0 = Release|Win32 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Release|x64.ActiveCfg = Release|x64 + {3BB99068-27C9-3C39-9082-A1577CB12BD2}.Release|x64.Build.0 = Release|x64 {E145B2B8-CD13-A6BE-B6A7-16E5A2148223}.Debug|Win32.ActiveCfg = Debug|Win32 {E145B2B8-CD13-A6BE-B6A7-16E5A2148223}.Debug|Win32.Build.0 = Debug|Win32 {E145B2B8-CD13-A6BE-B6A7-16E5A2148223}.Debug|x64.ActiveCfg = Debug|x64 @@ -267,6 +277,7 @@ Global {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} + {3BB99068-27C9-3C39-9082-A1577CB12BD2} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231} {E145B2B8-CD13-A6BE-B6A7-16E5A2148223} = {F3AB4ED5-5F37-BC99-6848-3F8ED452189A} {61F7EB00-7281-4BF3-9470-7C2EA92620C3} = {57B5AA5E-C340-1823-CC51-9B17385C7423} {C5ACCA6E-C04D-4B36-8516-3752B3C13C2F} = {57B5AA5E-C340-1823-CC51-9B17385C7423} -- cgit v1.2.3