From 24d2c5ce01c3cc05f78c47fa1d6ab0931fc86174 Mon Sep 17 00:00:00 2001 From: Sergey Kosarevsky Date: Sat, 3 Aug 2024 02:23:15 -0600 Subject: [PATCH] Added Slang example `001_HelloTriangle_Slang.cpp` --- CMakeLists.txt | 3 +- lvk/vulkan/CMakeLists.txt | 30 +++ samples/001_HelloTriangle_Slang.cpp | 318 ++++++++++++++++++++++++++++ samples/CMakeLists.txt | 7 + 4 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 samples/001_HelloTriangle_Slang.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 941b9fe026..24ac4c62ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ option(LVK_WITH_WAYLAND "Enable Wayland" OFF option(LVK_WITH_IMPLOT "Enable ImPlot" ON) option(LVK_WITH_OPENXR "Enable OpenXR" OFF) option(LVK_WITH_ANDROID_VALIDATION "Enable validation layers on Android" ON) +option(LVK_WITH_SLANG "Enable Slang compiler" ON) cmake_dependent_option(LVK_WITH_VULKAN_PORTABILITY "Enable portability extension" ON "APPLE" OFF) @@ -152,7 +153,7 @@ if(LVK_WITH_OPENXR) endif() # temporary -if(NOT LVK_USE_CUSTOM_MOLTENVK) +if(NOT LVK_USE_CUSTOM_MOLTENVK AND NOT LVK_WITH_SLANG) find_package(Vulkan REQUIRED) endif() diff --git a/lvk/vulkan/CMakeLists.txt b/lvk/vulkan/CMakeLists.txt index 6484bbc007..72fcce7962 100644 --- a/lvk/vulkan/CMakeLists.txt +++ b/lvk/vulkan/CMakeLists.txt @@ -35,6 +35,32 @@ lvk_set_folder(SPIRV "third-party/glslang") lvk_set_folder(glslang-default-resource-limits "third-party/glslang") # cmake-format: on +# slang +# cmake-format: off +if(LVK_WITH_SLANG) + set(SLANG_ENABLE_CUDA OFF CACHE BOOL "") + set(SLANG_ENABLE_OPTIX OFF CACHE BOOL "") + set(SLANG_ENABLE_NVAPI OFF CACHE BOOL "") + set(SLANG_ENABLE_XLIB OFF CACHE BOOL "") + set(SLANG_ENABLE_AFTERMATH OFF CACHE BOOL "") + set(SLANG_ENABLE_DX_ON_VK OFF CACHE BOOL "") + set(SLANG_ENABLE_GFX OFF CACHE BOOL "") + set(SLANG_ENABLE_SLANGC OFF CACHE BOOL "") + set(SLANG_ENABLE_SLANGRT ON CACHE BOOL "") + set(SLANG_ENABLE_SLANG_GLSLANG OFF CACHE BOOL "") + set(SLANG_ENABLE_TESTS OFF CACHE BOOL "") + set(SLANG_ENABLE_EXAMPLES OFF CACHE BOOL "") + set(SLANG_ENABLE_REPLAYER OFF CACHE BOOL "") + set(SLANG_ENABLE_PREBUILT_BINARIES OFF CACHE BOOL "") + add_subdirectory(${LVK_ROOT_DIR}/third-party/deps/src/slang "slang") + lvk_set_folder(compiler-core "third-party/slang") + lvk_set_folder(core "third-party/slang") + lvk_set_folder(slang "third-party/slang") + lvk_set_folder(slangd "third-party/slang") + lvk_set_folder(slang-rt "third-party/slang") +endif() +# cmake-format: on + # SPIRV-Reflect set(SPIRV_REFLECT_EXECUTABLE OFF CACHE BOOL "") set(SPIRV_REFLECT_STATIC_LIB ON CACHE BOOL "") @@ -52,6 +78,10 @@ endif() target_link_libraries(LVKVulkan PRIVATE LVKLibrary) target_link_libraries(LVKVulkan PRIVATE glslang SPIRV glslang-default-resource-limits) target_link_libraries(LVKVulkan PRIVATE spirv-reflect-static) +if(LVK_WITH_SLANG) + target_link_libraries(LVKVulkan PRIVATE slang) + target_link_libraries(LVKVulkan PRIVATE slang-rt) +endif() if(LVK_USE_CUSTOM_MOLTENVK) target_include_directories(LVKVulkan PUBLIC "${LVK_CUSTOM_MOLTENVK_PATH}/include") diff --git a/samples/001_HelloTriangle_Slang.cpp b/samples/001_HelloTriangle_Slang.cpp new file mode 100644 index 0000000000..ded13ddbb2 --- /dev/null +++ b/samples/001_HelloTriangle_Slang.cpp @@ -0,0 +1,318 @@ +/* +* LightweightVK +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +#include + +#include +#if defined(ANDROID) +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include + +const char* codeSlang = R"( +static const float2 pos[3] = float2[3]( + float2(-0.6, -0.4), + float2( 0.6, -0.4), + float2( 0.0, 0.6) +); +static const float3 col[3] = float3[3]( + float3(1.0, 0.0, 0.0), + float3(0.0, 1.0, 0.0), + float3(0.0, 0.0, 1.0) +); + +struct OutVertex { + float3 color; +}; + +struct Fragment { + float4 color; +}; + +struct VertexStageOutput { + OutVertex vertex : OutVertex; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexStageOutput vertexMain(uint vertexID : SV_VertexID) { + VertexStageOutput output; + + output.vertex.color = col[vertexID]; + output.sv_position = float4(pos[vertexID], 0.0, 1.0); + + return output; +} + +[shader("fragment")] +float4 fragmentMain(OutVertex vertex : OutVertex) : SV_Target { + return float4(vertex.color, 1.0); +} +)"; + +int width_ = 800; +int height_ = 600; +FramesPerSecondCounter fps_; + +lvk::Holder renderPipelineState_Triangle_; +std::unique_ptr ctx_; +lvk::Holder vert_; +lvk::Holder frag_; + +std::vector compileSlangToSPIRV(const char* code, lvk::ShaderStage stage) { + using namespace Slang; + + ComPtr slangGlobalSession; + if (SLANG_FAILED(slang::createGlobalSession(slangGlobalSession.writeRef()))) { + return {}; + } + + const slang::TargetDesc targetDesc = { + .format = SLANG_SPIRV, + .profile = slangGlobalSession->findProfile("spirv_1_6"), + .flags = SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY, + }; + + const slang::SessionDesc sessionDesc = { + .targets = &targetDesc, + .targetCount = 1, + }; + + ComPtr session; + if (SLANG_FAILED(slangGlobalSession->createSession(sessionDesc, session.writeRef()))) { + return {}; + } + + slang::IModule* slangModule = nullptr; + { + ComPtr diagnosticBlob; + slangModule = session->loadModuleFromSourceString("", "", code, diagnosticBlob.writeRef()); + if (diagnosticBlob) { + LLOGW("%s", (const char*)diagnosticBlob->getBufferPointer()); + } + if (!slangModule) { + return {}; + } + } + + ComPtr entryPointVert; + ComPtr entryPointFrag; + slangModule->findEntryPointByName("vertexMain", entryPointVert.writeRef()); + slangModule->findEntryPointByName("fragmentMain", entryPointFrag.writeRef()); + + Slang::List componentTypes; + componentTypes.add(slangModule); + int entryPointCount = 0; + int vertexEntryPointIndex = entryPointCount++; + componentTypes.add(entryPointVert); + int fragmentEntryPointIndex = entryPointCount++; + componentTypes.add(entryPointFrag); + + ComPtr composedProgram; + { + ComPtr diagnosticBlob; + SlangResult result = session->createCompositeComponentType( + componentTypes.getBuffer(), componentTypes.getCount(), composedProgram.writeRef(), diagnosticBlob.writeRef()); + if (diagnosticBlob) { + LLOGW("%s", (const char*)diagnosticBlob->getBufferPointer()); + } + if (SLANG_FAILED(result)) { + return {}; + } + } + + ComPtr spirvCode; + { + ComPtr diagnosticBlob; + const int entryPoint = stage == lvk::Stage_Vert ? vertexEntryPointIndex : fragmentEntryPointIndex; + SlangResult result = composedProgram->getEntryPointCode(entryPoint, 0, spirvCode.writeRef(), diagnosticBlob.writeRef()); + if (diagnosticBlob) { + LLOGW("%s", (const char*)diagnosticBlob->getBufferPointer()); + } + if (SLANG_FAILED(result)) { + return {}; + } + } + + const uint8_t* ptr = reinterpret_cast(spirvCode->getBufferPointer()); + + return std::vector(ptr, ptr + spirvCode->getBufferSize()); +} + +#include +#include +#include + +void init() { + { + auto spirv = compileSlangToSPIRV(codeSlang, lvk::Stage_Vert); + { + std::ofstream fout("dump.vert", std::ios::out | std::ios::binary); + fout.write(reinterpret_cast(spirv.data()), spirv.size()); + } + vert_ = ctx_->createShaderModule({spirv.data(), spirv.size(), lvk::Stage_Vert, "Shader Module: main (vert)"}); + } + { + auto spirv = compileSlangToSPIRV(codeSlang, lvk::Stage_Frag); + { + std::ofstream fout("dump.frag", std::ios::out | std::ios::binary); + fout.write(reinterpret_cast(spirv.data()), spirv.size()); + } + frag_ = ctx_->createShaderModule({spirv.data(), spirv.size(), lvk::Stage_Frag, "Shader Module: main (frag)"}); + } + + renderPipelineState_Triangle_ = ctx_->createRenderPipeline( + { + .smVert = vert_, + .smFrag = frag_, + .color = {{.format = ctx_->getSwapchainFormat()}}, + }, + nullptr); + + LVK_ASSERT(renderPipelineState_Triangle_.valid()); +} + +void destroy() { + vert_ = nullptr; + frag_ = nullptr; + renderPipelineState_Triangle_ = nullptr; + ctx_ = nullptr; +} + +void resize() { + if (!width_ || !height_) { + return; + } + ctx_->recreateSwapchain(width_, height_); +} + +void render() { + if (!width_ || !height_) { + return; + } + + lvk::ICommandBuffer& buffer = ctx_->acquireCommandBuffer(); + + // This will clear the framebuffer + buffer.cmdBeginRendering( + {.color = {{.loadOp = lvk::LoadOp_Clear, .clearColor = {1.0f, 1.0f, 1.0f, 1.0f}}}}, + {.color = {{.texture = ctx_->getCurrentSwapchainTexture()}}}); + buffer.cmdBindRenderPipeline(renderPipelineState_Triangle_); + buffer.cmdPushDebugGroupLabel("Render Triangle", 0xff0000ff); + buffer.cmdDraw(3); + buffer.cmdPopDebugGroupLabel(); + buffer.cmdEndRendering(); + ctx_->submit(buffer, ctx_->getCurrentSwapchainTexture()); +} + +#if !defined(ANDROID) +int main(int argc, char* argv[]) { + minilog::initialize(nullptr, {.threadNames = false}); + + GLFWwindow* window = lvk::initWindow("Vulkan Hello Triangle", width_, height_, true); + + ctx_ = lvk::createVulkanContextWithSwapchain(window, width_, height_, {}); + if (!ctx_) { + return 1; + } + init(); + + glfwSetFramebufferSizeCallback(window, [](GLFWwindow*, int width, int height) { + width_ = width; + height_ = height; + resize(); + }); + + double prevTime = glfwGetTime(); + + // main loop + while (!glfwWindowShouldClose(window)) { + const double newTime = glfwGetTime(); + fps_.tick(newTime - prevTime); + prevTime = newTime; + render(); + glfwPollEvents(); + } + + // destroy all the Vulkan stuff before closing the window + destroy(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} +#else +extern "C" { +void handle_cmd(android_app* app, int32_t cmd) { + switch (cmd) { + case APP_CMD_INIT_WINDOW: + if (app->window != nullptr) { + width_ = ANativeWindow_getWidth(app->window); + height_ = ANativeWindow_getHeight(app->window); + ctx_ = lvk::createVulkanContextWithSwapchain(app->window, width_, height_, {}); + init(); + } + break; + case APP_CMD_TERM_WINDOW: + destroy(); + break; + } +} + +void resize_callback(ANativeActivity* activity, ANativeWindow* window) { + int w = ANativeWindow_getWidth(window); + int h = ANativeWindow_getHeight(window); + if (width_ != w || height_ != h) { + width_ = w; + height_ = h; + if (ctx_) { + resize(); + } + } +} + +void android_main(android_app* app) { + minilog::initialize(nullptr, {.threadNames = false}); + app->onAppCmd = handle_cmd; + app->activity->callbacks->onNativeWindowResized = resize_callback; + + fps_.printFPS_ = false; + + timespec prevTime = {0, 0}; + clock_gettime(CLOCK_MONOTONIC, &prevTime); + + int events = 0; + android_poll_source* source = nullptr; + do { + timespec newTime = {0, 0}; + clock_gettime(CLOCK_MONOTONIC, &newTime); + fps_.tick(((double)newTime.tv_sec + 1.0e-9 * newTime.tv_nsec) - + ((double)prevTime.tv_sec + 1.0e-9 * prevTime.tv_nsec)); + LLOGL("FPS: %.1f\n", fps_.getFPS()); + prevTime = newTime; + if (ctx_) { + render(); + } + if (ALooper_pollOnce(0, nullptr, &events, (void**)&source) >= 0) { + if (source) { + source->process(app, source); + } + } + } while (!app->destroyRequested); +} +} // extern "C" +#endif diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 3051f4d146..e749090587 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -136,3 +136,10 @@ ADD_DEMO_SOURCES("Tiny_MeshLarge" ADD_DEMO_SOURCES("Tiny_MeshLarge" "${LVK_ROOT_DIR}/third-party/deps/src/imgui/imgui_demo.cpp") ADD_DEMO_LINK_LIBRARIES("Tiny_MeshLarge" ktx) + +# Slang +if(LVK_WITH_SLANG) + ADD_DEMO("001_HelloTriangle_Slang") + target_link_libraries(001_HelloTriangle_Slang PRIVATE slang-rt) + target_link_libraries(001_HelloTriangle_Slang PRIVATE core) +endif()