diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index bae0d0a..5597f66 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,7 +1,9 @@ set(CMAKE_CXX_STANDARD 17) -# Enable SDL Vulkan integration -set(PLUME_SDL_VULKAN_ENABLED ON CACHE BOOL "Enable SDL Vulkan integration" FORCE) +# Enable SDL Vulkan integration (Linux only) +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + set(PLUME_SDL_VULKAN_ENABLED ON CACHE BOOL "Enable SDL Vulkan integration" FORCE) +endif() # Find SDL2 (required for examples) find_package(SDL2 REQUIRED) diff --git a/examples/cube/main.cpp b/examples/cube/main.cpp index a2aeae8..f31e2b1 100644 --- a/examples/cube/main.cpp +++ b/examples/cube/main.cpp @@ -507,36 +507,28 @@ namespace plume { ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); } - void CubeExample(RenderInterface* renderInterface, const std::string& apiName) { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); - return; - } - - uint32_t flags = SDL_WINDOW_RESIZABLE; -#if defined(__APPLE__) - flags |= SDL_WINDOW_METAL; -#endif - + void CubeExample(RenderInterface* renderInterface, SDL_Window* window, const std::string& apiName) { std::string windowTitle = "Plume Cube Texture Example (" + apiName + ")"; - SDL_Window* window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); - if (!window) { - fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); - SDL_Quit(); - return; - } + SDL_SetWindowTitle(window, windowTitle.c_str()); + CubeContext ctx; +#if PLUME_SDL_VULKAN_ENABLED + createContext(ctx, renderInterface, window, apiName); +#elif defined(__linux__) SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); - - CubeContext ctx; -#if defined(__linux__) createContext(ctx, renderInterface, { wmInfo.info.x11.display, wmInfo.info.x11.window }, apiName); #elif defined(__APPLE__) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); SDL_MetalView view = SDL_Metal_CreateView(window); createContext(ctx, renderInterface, { wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) }, apiName); #elif defined(WIN32) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); createContext(ctx, renderInterface, { wmInfo.info.win.window }, apiName); #endif @@ -577,17 +569,19 @@ namespace plume { #if defined(__APPLE__) SDL_Metal_DestroyView(view); #endif - SDL_DestroyWindow(window); - SDL_Quit(); } } -std::unique_ptr CreateRenderInterface(std::string& apiName) { +std::unique_ptr CreateRenderInterface(SDL_Window* window, std::string& apiName) { const bool useVulkan = false; #if defined(_WIN32) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "D3D12"; return plume::CreateD3D12Interface(); @@ -595,20 +589,57 @@ std::unique_ptr CreateRenderInterface(std::string& apiNa #elif defined(__APPLE__) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "Metal"; return plume::CreateMetalInterface(); } #else apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); #endif +#endif } int main(int argc, char* argv[]) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); + return 1; + } + + uint32_t flags = SDL_WINDOW_RESIZABLE; +#if PLUME_SDL_VULKAN_ENABLED + flags |= SDL_WINDOW_VULKAN; +#elif defined(__APPLE__) + flags |= SDL_WINDOW_METAL; +#endif + + SDL_Window* window = SDL_CreateWindow("Plume Cube Texture Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); + if (!window) { + fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + std::string apiName = "Unknown"; - auto renderInterface = CreateRenderInterface(apiName); - plume::CubeExample(renderInterface.get(), apiName); + auto renderInterface = CreateRenderInterface(window, apiName); + if (!renderInterface) { + fprintf(stderr, "Failed to create render interface\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + plume::CubeExample(renderInterface.get(), window, apiName); + + SDL_DestroyWindow(window); + SDL_Quit(); return 0; } diff --git a/examples/cube/shaders/cube.frag.hlsl b/examples/cube/shaders/cube.frag.hlsl index c66f3db..182e489 100644 --- a/examples/cube/shaders/cube.frag.hlsl +++ b/examples/cube/shaders/cube.frag.hlsl @@ -2,7 +2,7 @@ // Samples from a cubemap texture [[vk::binding(0, 0)]] TextureCube cubeTexture : register(t0); -[[vk::binding(1, 0)]] SamplerState cubeSampler : register(s0); +[[vk::binding(1, 0)]] SamplerState cubeSampler : register(s1); struct PSInput { float4 position : SV_POSITION; diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp index 503a18a..5d573c7 100644 --- a/examples/triangle/main.cpp +++ b/examples/triangle/main.cpp @@ -293,36 +293,28 @@ namespace plume { ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); } - void RenderInterfaceTest(RenderInterface* renderInterface, const std::string &apiName) { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); - return; - } - - uint32_t flags = SDL_WINDOW_RESIZABLE; -#if defined(__APPLE__) - flags |= SDL_WINDOW_METAL; -#endif - + void RenderInterfaceTest(RenderInterface* renderInterface, SDL_Window* window, const std::string &apiName) { std::string windowTitle = "Plume Example (" + apiName + ")"; - SDL_Window* window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); - if (!window) { - fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); - SDL_Quit(); - return; - } + SDL_SetWindowTitle(window, windowTitle.c_str()); + TestContext ctx; +#if PLUME_SDL_VULKAN_ENABLED + createContext(ctx, renderInterface, window, apiName); +#elif defined(__linux__) SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); - - TestContext ctx; -#if defined(__linux__) createContext(ctx, renderInterface, { wmInfo.info.x11.display, wmInfo.info.x11.window }, apiName); #elif defined(__APPLE__) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); SDL_MetalView view = SDL_Metal_CreateView(window); createContext(ctx, renderInterface, { wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) }, apiName); #elif defined(WIN32) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); createContext(ctx, renderInterface, { wmInfo.info.win.window }, apiName); #endif @@ -365,17 +357,19 @@ namespace plume { #if defined(__APPLE__) SDL_Metal_DestroyView(view); #endif - SDL_DestroyWindow(window); - SDL_Quit(); } } -std::unique_ptr CreateRenderInterface(std::string &apiName) { +std::unique_ptr CreateRenderInterface(SDL_Window* window, std::string &apiName) { const bool useVulkan = false; #if defined(_WIN32) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "D3D12"; @@ -384,7 +378,11 @@ std::unique_ptr CreateRenderInterface(std::string &apiNa #elif defined(__APPLE__) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "Metal"; @@ -392,13 +390,46 @@ std::unique_ptr CreateRenderInterface(std::string &apiNa } #else apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); #endif +#endif } int main(int argc, char* argv[]) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); + return 1; + } + + uint32_t flags = SDL_WINDOW_RESIZABLE; +#if PLUME_SDL_VULKAN_ENABLED + flags |= SDL_WINDOW_VULKAN; +#elif defined(__APPLE__) + flags |= SDL_WINDOW_METAL; +#endif + + SDL_Window* window = SDL_CreateWindow("Plume Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); + if (!window) { + fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + std::string apiName = "Unknown"; - auto renderInterface = CreateRenderInterface(apiName); - plume::RenderInterfaceTest(renderInterface.get(), apiName); + auto renderInterface = CreateRenderInterface(window, apiName); + if (!renderInterface) { + fprintf(stderr, "Failed to create render interface\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + plume::RenderInterfaceTest(renderInterface.get(), window, apiName); + + SDL_DestroyWindow(window); + SDL_Quit(); return 0; } diff --git a/plume_d3d12.cpp b/plume_d3d12.cpp index db6cf33..4fb00fe 100644 --- a/plume_d3d12.cpp +++ b/plume_d3d12.cpp @@ -314,7 +314,7 @@ namespace plume { return D3D12_BLEND_OP_ADD; } } - + static D3D12_COLOR_WRITE_ENABLE toD3D12(RenderColorWriteEnable enable) { return D3D12_COLOR_WRITE_ENABLE( ((uint32_t(enable) & uint32_t(RenderColorWriteEnable::RED)) ? D3D12_COLOR_WRITE_ENABLE_RED : 0x0) | @@ -363,7 +363,7 @@ namespace plume { return D3D12_LOGIC_OP_CLEAR; } } - + static D3D12_FILTER toFilter(RenderFilter minFilter, RenderFilter magFilter, RenderMipmapMode mipmapMode, bool anisotropyEnabled, bool comparisonEnabled) { assert(minFilter != RenderFilter::UNKNOWN); assert(magFilter != RenderFilter::UNKNOWN); @@ -534,11 +534,11 @@ namespace plume { case RenderPrimitiveTopology::POINT_LIST: return D3D_PRIMITIVE_TOPOLOGY_POINTLIST; case RenderPrimitiveTopology::LINE_LIST: - return D3D_PRIMITIVE_TOPOLOGY_LINELIST; + return D3D_PRIMITIVE_TOPOLOGY_LINELIST; case RenderPrimitiveTopology::LINE_STRIP: return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP; case RenderPrimitiveTopology::TRIANGLE_LIST: - return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; case RenderPrimitiveTopology::TRIANGLE_STRIP: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; # ifdef PLUME_D3D12_AGILITY_SDK_ENABLED @@ -642,7 +642,7 @@ namespace plume { // Fall back to common state. return D3D12_RESOURCE_STATE_COMMON; } - + static D3D12_RESOURCE_STATES toTextureState(RenderBarrierStages stages, RenderTextureLayout textureLayout, RenderTextureFlags textureFlags) { switch (textureLayout) { case RenderTextureLayout::GENERAL: @@ -728,15 +728,15 @@ namespace plume { case RenderSwizzle::IDENTITY: return identity; case RenderSwizzle::ZERO: - return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0; + return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0; case RenderSwizzle::ONE: - return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1; + return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1; case RenderSwizzle::R: - return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0; + return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0; case RenderSwizzle::G: - return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1; + return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1; case RenderSwizzle::B: - return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2; + return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2; case RenderSwizzle::A: return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_3; default: @@ -798,7 +798,7 @@ namespace plume { fprintf(stderr, "CreateDescriptorHeap failed with error code 0x%lX.\n", res); return; } - + cpuDescriptorHandle = heap->GetCPUDescriptorHandleForHeapStart(); if (shaderVisible) { @@ -1278,7 +1278,7 @@ namespace plume { setSRV(descriptorIndex, nullptr, nullptr); } } - + void D3D12DescriptorSet::setSRV(uint32_t descriptorIndex, ID3D12Resource *resource, const D3D12_SHADER_RESOURCE_VIEW_DESC *viewDesc) { if ((resource != nullptr) || (viewDesc != nullptr)) { uint32_t descriptorIndexClamped = std::min(descriptorIndex, descriptorTypeMaxIndex); @@ -1324,7 +1324,7 @@ namespace plume { this->textureCount = textureCount; this->format = format; this->maxFrameLatency = maxFrameLatency; - + // Store the native format representation. nativeFormat = toDXGI(format); @@ -1334,7 +1334,7 @@ namespace plume { if (commandQueue->device->renderInterface->allowTearing) { swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; } - + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; swapChainDesc.BufferCount = textureCount; swapChainDesc.Width = width; @@ -1497,12 +1497,12 @@ namespace plume { } // D3D12Framebuffer - + D3D12Framebuffer::D3D12Framebuffer(D3D12Device *device, const RenderFramebufferDesc &desc) { assert(device != nullptr); this->device = device; - + if (desc.colorAttachmentsCount > 0) { for (uint32_t i = 0; i < desc.colorAttachmentsCount; i++) { const D3D12TextureView *interfaceTextureView = desc.colorAttachmentViews && desc.colorAttachmentViews[i] ? static_cast(desc.colorAttachmentViews[i]) : nullptr; @@ -1892,11 +1892,11 @@ namespace plume { activeStencilRef = 0; descriptorHeapsSet = false; } - + void D3D12CommandList::barriers(RenderBarrierStages stages, const RenderBufferBarrier *bufferBarriers, uint32_t bufferBarriersCount, const RenderTextureBarrier *textureBarriers, uint32_t textureBarriersCount) { thread_local std::vector barrierVector; barrierVector.clear(); - + auto makeBarrier = [&](ID3D12Resource *resource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter, bool supportsUAV, D3D12_RESOURCE_BARRIER &resourceBarrier) { resourceBarrier = {}; @@ -1935,7 +1935,7 @@ namespace plume { interfaceBuffer->resourceStates = stateAfter; } - + bool resetSamplePositionsRequired = false; for (uint32_t i = 0; i < textureBarriersCount; i++) { const RenderTextureBarrier &textureBarrier = textureBarriers[i]; @@ -1948,7 +1948,7 @@ namespace plume { if (!madeBarrier) { continue; } - + // MSAA Depth targets with multisampling require separate barriers. const bool msaaDepthTarget = (interfaceTexture->desc.flags & RenderTextureFlag::DEPTH_TARGET) && (interfaceTexture->desc.multisampling.sampleCount > 1); if (msaaDepthTarget && interfaceTexture->desc.multisampling.sampleLocationsEnabled) { @@ -1964,7 +1964,7 @@ namespace plume { if (resetSamplePositionsRequired) { resetSamplePositions(); } - + if (!barrierVector.empty()) { d3d->ResourceBarrier(UINT(barrierVector.size()), barrierVector.data()); } @@ -2004,7 +2004,7 @@ namespace plume { desc.Depth = depth; d3d->DispatchRays(&desc); } - + void D3D12CommandList::drawInstanced(uint32_t vertexCountPerInstance, uint32_t instanceCount, uint32_t startVertexLocation, uint32_t startInstanceLocation) { assert(activeGraphicsPipelineLayout != nullptr); checkTopology(); @@ -2131,7 +2131,7 @@ namespace plume { d3d->IASetIndexBuffer(nullptr); } } - + void D3D12CommandList::setVertexBuffers(uint32_t startSlot, const RenderVertexBufferView *views, uint32_t viewCount, const RenderInputSlot *inputSlots) { if (views != nullptr) { assert(inputSlots != nullptr); @@ -2200,14 +2200,14 @@ namespace plume { d3d->RSSetScissorRects(1, &scissor); } } - + void D3D12CommandList::setFramebuffer(const RenderFramebuffer *framebuffer) { if (framebuffer != nullptr) { const D3D12Framebuffer *interfaceFramebuffer = static_cast(framebuffer); for (const D3D12Texture *target : interfaceFramebuffer->colorTargets) { assert((target->layout == RenderTextureLayout::COLOR_WRITE) && "Color targets must be in color write layout when setting the framebuffer."); } - + if (interfaceFramebuffer->depthTarget != nullptr) { const bool depthReadLayout = (interfaceFramebuffer->depthTarget->layout == RenderTextureLayout::DEPTH_READ); const bool depthWriteLayout = (interfaceFramebuffer->depthTarget->layout == RenderTextureLayout::DEPTH_WRITE); @@ -2361,7 +2361,7 @@ namespace plume { const D3D12AccelerationStructure *interfaceAccelerationStructure = static_cast(dstAccelerationStructure); assert(interfaceAccelerationStructure->type == RenderAccelerationStructureType::BOTTOM_LEVEL); - + const D3D12Buffer *interfaceScratchBuffer = static_cast(scratchBuffer.ref); assert((interfaceScratchBuffer->desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_SCRATCH) && "Scratch buffer must be allowed."); @@ -2454,7 +2454,7 @@ namespace plume { activeStencilRef = graphicsPipeline->stencilRef; } } - + void D3D12CommandList::checkFramebufferSamplePositions() { if (!targetFramebufferSamplePositionsSet && (targetFramebuffer != nullptr)) { if (targetFramebuffer->depthTarget != nullptr) { @@ -2464,7 +2464,7 @@ namespace plume { targetFramebufferSamplePositionsSet = true; } } - + void D3D12CommandList::setSamplePositions(const RenderTexture *texture) { assert(texture != nullptr); @@ -2684,7 +2684,7 @@ namespace plume { interfaceSemaphore->semaphoreValue++; d3d->Signal(interfaceSemaphore->d3d, interfaceSemaphore->semaphoreValue); } - + if (signalFence != nullptr) { D3D12CommandFence *interfaceFence = static_cast(signalFence); d3d->Signal(interfaceFence->d3d, interfaceFence->fenceValue); @@ -2711,7 +2711,15 @@ namespace plume { D3D12_RESOURCE_DESC resourceDesc = {}; resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - resourceDesc.Width = desc.size; + + // Constant buffers must be aligned to D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT (256 bytes) + // to allow creating CBVs that cover the full aligned size. + if (desc.flags & RenderBufferFlag::CONSTANT) { + resourceDesc.Width = roundUp(desc.size, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + } else { + resourceDesc.Width = desc.size; + } + resourceDesc.Height = 1; resourceDesc.DepthOrArraySize = 1; resourceDesc.MipLevels = 1; @@ -2727,7 +2735,7 @@ namespace plume { if ((desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE)) { resourceStates |= D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE; } - + // Resources on upload heap require generic read during creation. if (desc.heapType == RenderHeapType::UPLOAD) { resourceStates |= D3D12_RESOURCE_STATE_GENERIC_READ; @@ -3129,7 +3137,7 @@ namespace plume { for (uint32_t i = 0; i < desc.renderTargetCount; i++) { psoDesc.RTVFormats[i] = toDXGI(desc.renderTargetFormat[i]); - + const RenderBlendDesc &renderDesc = desc.renderTargetBlend[i]; D3D12_RENDER_TARGET_BLEND_DESC &targetDesc = psoDesc.BlendState.RenderTarget[i]; targetDesc.BlendEnable = renderDesc.blendEnabled; @@ -3562,7 +3570,7 @@ namespace plume { for (uint32_t i = 0; i < desc.rootDescriptorDescsCount; i++) { const RenderRootDescriptorDesc& rootDescriptorDesc = desc.rootDescriptorDescs[i]; - D3D12_ROOT_PARAMETER rootParameter = {}; + D3D12_ROOT_PARAMETER rootParameter = {}; rootParameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; rootParameter.Descriptor.ShaderRegister = rootDescriptorDesc.shaderRegister; rootParameter.Descriptor.RegisterSpace = rootDescriptorDesc.registerSpace; @@ -3626,7 +3634,7 @@ namespace plume { assert(renderInterface != nullptr); this->renderInterface = renderInterface; - + // Detect adapter to use that will offer the best performance and features. HRESULT res; UINT adapterIndex = 0; @@ -3781,7 +3789,7 @@ namespace plume { D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; allocatorDesc.pDevice = d3d; allocatorDesc.pAdapter = adapter; - allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | + allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | D3D12MA::ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED | D3D12MA::ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED; res = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); @@ -3820,7 +3828,7 @@ namespace plume { D3D12_MESSAGE_ID_SAMPLEPOSITIONS_MISMATCH_DEFERRED, # endif }; - + D3D12_INFO_QUEUE_FILTER newFilter = {}; newFilter.DenyList.NumSeverities = _countof(severities); newFilter.DenyList.pSeverityList = severities; @@ -3833,7 +3841,7 @@ namespace plume { infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, D3D12_DEBUG_LAYER_BREAK_ON_WARNING); } # endif - + // Fill capabilities. capabilities.descriptorIndexing = true; capabilities.scalarBlockLayout = true; @@ -3900,7 +3908,7 @@ namespace plume { std::unique_ptr D3D12Device::createCommandQueue(RenderCommandListType type) { return std::make_unique(this, type); } - + std::unique_ptr D3D12Device::createBuffer(const RenderBufferDesc &desc) { if ((desc.heapType == RenderHeapType::GPU_UPLOAD) && gpuUploadHeapFallback) { return std::make_unique(this, customUploadPool.get(), desc); @@ -4059,7 +4067,7 @@ namespace plume { tableInfo.tableBufferData.clear(); tableInfo.tableBufferData.resize(tableSize, 0); - + thread_local std::vector descriptorHandles; descriptorHandles.clear(); descriptorHandles.resize(raytracingPipeline->pipelineLayout->rootCount, 0); @@ -4230,7 +4238,7 @@ namespace plume { } // Global creation function. - + std::unique_ptr CreateD3D12Interface() { std::unique_ptr createdInterface = std::make_unique(); return createdInterface->isValid() ? std::move(createdInterface) : nullptr; diff --git a/plume_vulkan.cpp b/plume_vulkan.cpp index d73fb1c..359182f 100644 --- a/plume_vulkan.cpp +++ b/plume_vulkan.cpp @@ -6,7 +6,7 @@ // #define VMA_IMPLEMENTATION -#define VOLK_IMPLEMENTATION +#define VOLK_IMPLEMENTATION #include "plume_vulkan.h" @@ -65,11 +65,11 @@ namespace plume { VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, # endif }; - + static const std::unordered_set RequiredDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; - + static const std::unordered_set OptionalDeviceExtensions = { VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, @@ -342,9 +342,9 @@ namespace plume { case RenderPrimitiveTopology::LINE_STRIP: return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; case RenderPrimitiveTopology::TRIANGLE_LIST: - return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; case RenderPrimitiveTopology::TRIANGLE_STRIP: - return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; case RenderPrimitiveTopology::TRIANGLE_FAN: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; default: @@ -599,7 +599,7 @@ namespace plume { return VK_ACCELERATION_STRUCTURE_TYPE_MAX_ENUM_KHR; } } - + static VkPipelineStageFlags toStageFlags(RenderBarrierStages stages, bool geometrySupported, bool rtSupported) { VkPipelineStageFlags flags = 0; @@ -688,7 +688,7 @@ namespace plume { flags |= preferFastTrace ? VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR : 0; return flags; } - + static VkImageLayout toImageLayout(RenderTextureLayout layout) { switch (layout) { case RenderTextureLayout::UNKNOWN: @@ -918,7 +918,7 @@ namespace plume { } void VulkanBuffer::setName(const std::string &name) { - setObjectName(device->vk, VK_OBJECT_TYPE_IMAGE, uint64_t(vk), name); + setObjectName(device->vk, VK_OBJECT_TYPE_BUFFER, uint64_t(vk), name); } uint64_t VulkanBuffer::getDeviceAddress() const { @@ -1031,7 +1031,7 @@ namespace plume { vmaDestroyImage(device->allocator, vk, allocation); } } - + void VulkanTexture::createImageView(VkFormat format) { VkImageView view = VK_NULL_HANDLE; VkImageViewCreateInfo viewInfo = {}; @@ -1044,7 +1044,7 @@ namespace plume { viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; viewInfo.subresourceRange = imageSubresourceRange; - + VkResult res = vkCreateImageView(device->vk, &viewInfo, nullptr, &imageView); if (res != VK_SUCCESS) { fprintf(stderr, "vkCreateImageView failed with error code 0x%X.\n", res); @@ -1160,7 +1160,7 @@ namespace plume { } } } - + // Create bindings. uint32_t immutableSamplerIndex = 0; for (uint32_t i = 0; i < descriptorSetDesc.descriptorRangesCount; i++) { @@ -1189,7 +1189,7 @@ namespace plume { setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; setLayoutInfo.pBindings = !setBindings.empty() ? setBindings.data() : nullptr; setLayoutInfo.bindingCount = uint32_t(setBindings.size()); - + thread_local std::vector bindingFlags; VkDescriptorSetLayoutBindingFlagsCreateInfo flagsInfo = {}; if (descriptorSetDesc.lastRangeIsBoundless && (descriptorSetDesc.descriptorRangesCount > 0)) { @@ -1204,7 +1204,7 @@ namespace plume { setLayoutInfo.pNext = &flagsInfo; setLayoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT; } - + VkResult res = vkCreateDescriptorSetLayout(device->vk, &setLayoutInfo, nullptr, &vk); if (res != VK_SUCCESS) { fprintf(stderr, "vkCreateDescriptorSetLayout failed with error code 0x%X.\n", res); @@ -1580,7 +1580,7 @@ namespace plume { colorBlend.logicOp = toVk(desc.logicOp); colorBlend.pAttachments = !colorBlendAttachments.empty() ? colorBlendAttachments.data() : nullptr; colorBlend.attachmentCount = uint32_t(colorBlendAttachments.size()); - + VkPipelineDepthStencilStateCreateInfo depthStencil = {}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = desc.depthEnabled; @@ -1860,7 +1860,7 @@ namespace plume { groupCount = pipelineInfo.groupCount; } - + VulkanRaytracingPipeline::~VulkanRaytracingPipeline() { if (vk != VK_NULL_HANDLE) { vkDestroyPipeline(device->vk, vk, nullptr); @@ -1886,7 +1886,7 @@ namespace plume { thread_local std::unordered_map typeCounts; typeCounts.clear(); - + uint32_t boundlessRangeSize = 0; uint32_t rangeCount = desc.descriptorRangesCount; if (desc.lastRangeIsBoundless) { @@ -1940,7 +1940,7 @@ namespace plume { delete setLayout; } - + void VulkanDescriptorSet::setBuffer(uint32_t descriptorIndex, const RenderBuffer *buffer, uint64_t bufferSize, const RenderBufferStructuredView *bufferStructuredView, const RenderBufferFormattedView *bufferFormattedView) { if (buffer == nullptr) { return; @@ -2132,7 +2132,7 @@ namespace plume { assert(renderWindow.view != 0); // Creates a wrapper around the window for storing and fetching sizes. this->windowWrapper = std::make_unique(renderWindow.window); - + VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; surfaceCreateInfo.pLayer = renderWindow.view; @@ -2275,7 +2275,7 @@ namespace plume { presentId.swapchainCount = 1; presentInfo.pNext = &presentId; } - + VkResult res; { const std::scoped_lock queueLock(*commandQueue->queue->mutex); @@ -2314,17 +2314,23 @@ namespace plume { // Destroy any image view references to the current swap chain. releaseImageViews(); - // We don't actually need to query the surface capabilities but the validation layer seems to cache the valid extents from this call. + // Query surface capabilities to get the valid extent bounds. VkSurfaceCapabilitiesKHR surfaceCapabilities = {}; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(commandQueue->device->physicalDevice, surface, &surfaceCapabilities); + // Clamp the extent to the surface capabilities' min/max bounds. + // This is required because the window size may differ from the valid surface extent + // (e.g., due to window decorations, compositor behavior, or timing issues). + uint32_t clampedWidth = std::clamp(width, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width); + uint32_t clampedHeight = std::clamp(height, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height); + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; createInfo.minImageCount = textureCount; createInfo.imageFormat = pickedSurfaceFormat.format; createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace; - createInfo.imageExtent.width = width; - createInfo.imageExtent.height = height; + createInfo.imageExtent.width = clampedWidth; + createInfo.imageExtent.height = clampedHeight; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; @@ -2371,12 +2377,16 @@ namespace plume { // Assign the swap chain images to the buffer resources. textures.resize(textureCount); + // Update the stored width/height to reflect the actual swapchain extent. + width = clampedWidth; + height = clampedHeight; + for (uint32_t i = 0; i < textureCount; i++) { textures[i] = VulkanTexture(commandQueue->device, images[i]); textures[i].desc.dimension = RenderTextureDimension::TEXTURE_2D; textures[i].desc.format = format; - textures[i].desc.width = width; - textures[i].desc.height = height; + textures[i].desc.width = clampedWidth; + textures[i].desc.height = clampedHeight; textures[i].desc.depth = 1; textures[i].desc.mipLevels = 1; textures[i].desc.arraySize = 1; @@ -2495,7 +2505,7 @@ namespace plume { } // VulkanFramebuffer - + VulkanFramebuffer::VulkanFramebuffer(VulkanDevice *device, const RenderFramebufferDesc &desc) { assert(device != nullptr); @@ -2614,7 +2624,7 @@ namespace plume { fprintf(stderr, "vkCreateRenderPass failed with error code 0x%X.\n", res); return; } - + VkFramebufferCreateInfo fbInfo = {}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = renderPass; @@ -2673,13 +2683,13 @@ namespace plume { createInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; createInfo.queryType = VK_QUERY_TYPE_TIMESTAMP; createInfo.queryCount = queryCount; - + VkResult res = vkCreateQueryPool(device->vk, &createInfo, nullptr, &vk); if (res != VK_SUCCESS) { fprintf(stderr, "vkCreateQueryPool failed with error code 0x%X.\n", res); return; } - + results.resize(queryCount); } @@ -2701,16 +2711,16 @@ namespace plume { uint64_t t = (u1 * v1); uint64_t w3 = (t & 0xffffffff); uint64_t k = (t >> 32); - + u >>= 32; t = (u * v1) + k; k = (t & 0xffffffff); uint64_t w1 = (t >> 32); - + v >>= 32; t = (u1 * v) + k; k = (t >> 32); - + h = (u * v) + w1 + k; l = (t << 32) + w3; }; @@ -2861,7 +2871,7 @@ namespace plume { interfaceTexture->textureLayout = textureBarrier.layout; interfaceTexture->barrierStages = stages; } - + if (bufferMemoryBarriers.empty() && imageMemoryBarriers.empty()) { return; } @@ -2915,7 +2925,7 @@ namespace plume { vkCmdDraw(vk, vertexCountPerInstance, instanceCount, startVertexLocation, startInstanceLocation); } - + void VulkanCommandList::drawIndexedInstanced(uint32_t indexCountPerInstance, uint32_t instanceCount, uint32_t startIndexLocation, int32_t baseVertexLocation, uint32_t startInstanceLocation) { assert(activeGraphicsPipelineLayout != nullptr); checkActiveRenderPass(); @@ -2958,7 +2968,7 @@ namespace plume { void VulkanCommandList::setComputePushConstants(uint32_t rangeIndex, const void *data, uint32_t offset, uint32_t size) { assert(activeComputePipelineLayout != nullptr); assert(rangeIndex < activeComputePipelineLayout->pushConstantRanges.size()); - + const VkPushConstantRange &range = activeComputePipelineLayout->pushConstantRanges[rangeIndex]; vkCmdPushConstants(vk, activeComputePipelineLayout->vk, range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT, range.offset + offset, size == 0 ? range.size : size, data); } @@ -3184,7 +3194,7 @@ namespace plume { void VulkanCommandList::copyTextureRegion(const RenderTextureCopyLocation &dstLocation, const RenderTextureCopyLocation &srcLocation, uint32_t dstX, uint32_t dstY, uint32_t dstZ, const RenderBox *srcBox) { endActiveRenderPass(); - + assert(dstLocation.type != RenderTextureCopyType::UNKNOWN); assert(srcLocation.type != RenderTextureCopyType::UNKNOWN); @@ -3253,7 +3263,7 @@ namespace plume { assert(dstBuffer != nullptr); assert(srcBuffer != nullptr); - + const VulkanBuffer *interfaceDstBuffer = static_cast(dstBuffer); const VulkanBuffer *interfaceSrcBuffer = static_cast(srcBuffer); VkBufferCopy bufferCopy = {}; @@ -3348,7 +3358,7 @@ namespace plume { vkCmdResolveImage(vk, src->vk, srcLayout, dst->vk, dstLayout, uint32_t(imageResolves.size()), imageResolves.data()); } - + void VulkanCommandList::buildBottomLevelAS(const RenderAccelerationStructure *dstAccelerationStructure, RenderBufferReference scratchBuffer, const RenderBottomLevelASBuildInfo &buildInfo) { assert(dstAccelerationStructure != nullptr); assert(scratchBuffer.ref != nullptr); @@ -3452,7 +3462,7 @@ namespace plume { void VulkanCommandList::checkActiveRenderPass() { assert(targetFramebuffer != nullptr); - + if (activeRenderPass == VK_NULL_HANDLE) { VkRenderPassBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -3614,7 +3624,7 @@ namespace plume { return; } } - + void VulkanCommandQueue::waitForCommandFence(RenderCommandFence *fence) { assert(fence != nullptr); @@ -3684,7 +3694,7 @@ namespace plume { std::unique_ptr VulkanPool::createTexture(const RenderTextureDesc &desc) { return std::make_unique(device, this, desc); } - + // VulkanQueueFamily void VulkanQueueFamily::add(VulkanCommandQueue *virtualQueue) { @@ -3717,7 +3727,7 @@ namespace plume { } // VulkanDevice - + VulkanDevice::VulkanDevice(VulkanInterface *renderInterface, const std::string &preferredDeviceName) { assert(renderInterface != nullptr); @@ -3729,7 +3739,7 @@ namespace plume { fprintf(stderr, "Unable to find devices that support Vulkan.\n"); return; } - + std::vector physicalDevices(deviceCount); vkEnumeratePhysicalDevices(renderInterface->instance, &deviceCount, physicalDevices.data()); @@ -3800,7 +3810,7 @@ namespace plume { } # endif } - + if (!missingRequiredExtensions.empty()) { for (const std::string &extension : missingRequiredExtensions) { fprintf(stderr, "Missing required extension: %s.\n", extension.c_str()); @@ -3948,7 +3958,7 @@ namespace plume { bufferDeviceAddressFeatures.pNext = createDeviceChain; createDeviceChain = &bufferDeviceAddressFeatures; } - + if (portabilityFound) { portabilityFeatures.pNext = createDeviceChain; createDeviceChain = &portabilityFeatures; @@ -4333,7 +4343,7 @@ namespace plume { buildInfo.scratchSize = roundUp(buildSizesInfo.buildScratchSize, AccelerationStructureBufferAlignment); buildInfo.accelerationStructureSize = roundUp(buildSizesInfo.accelerationStructureSize, AccelerationStructureBufferAlignment); } - + void VulkanDevice::setShaderBindingTableInfo(RenderShaderBindingTableInfo &tableInfo, const RenderShaderBindingGroups &groups, const RenderPipeline *pipeline, RenderDescriptorSet **descriptorSets, uint32_t descriptorSetCount) { assert(pipeline != nullptr); assert((descriptorSets != nullptr) && "Vulkan doesn't require descriptor sets, but they should be passed to keep consistency with D3D12."); @@ -4351,7 +4361,7 @@ namespace plume { fprintf(stderr, "vkGetRayTracingShaderGroupHandlesKHR failed with error code 0x%X.\n", res); return; } - + const uint32_t handleSizeAligned = roundUp(handleSize, rtPipelineProperties.shaderGroupHandleAlignment); const uint32_t regionAlignment = roundUp(handleSizeAligned, rtPipelineProperties.shaderGroupBaseAlignment); uint64_t tableSize = 0; @@ -4485,7 +4495,7 @@ namespace plume { # if PLUME_SDL_VULKAN_ENABLED // Push the extensions specified by SDL as required. - // SDL2 has this awkward requirement for the window to pull the extensions from. + // SDL2 has this awkward requirement for the window to pull the extensions from. // This can be removed when upgrading to SDL3. if (sdlWindow != nullptr) { uint32_t sdlVulkanExtensionCount = 0; @@ -4544,7 +4554,7 @@ namespace plume { std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - + const char validationLayerName[] = "VK_LAYER_KHRONOS_validation"; const char *enabledLayerNames[] = { validationLayerName }; for (const VkLayerProperties &layerProperties : availableLayers) { @@ -4555,7 +4565,7 @@ namespace plume { } } # endif - + res = vkCreateInstance(&createInfo, nullptr, &instance); if (res != VK_SUCCESS) { fprintf(stderr, "vkCreateInstance failed with error code 0x%X.\n", res);