Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -117,6 +117,7 @@ ; glarb - GL with legacy assembler-like shaders, might used only for buggy drivers. ; gl - GL with GLSL shaders, should be used by default. ; dummy - backend that does nothing, allows to check performance without backend drivers. +; vulkan - Vulkan with SPIR-V shaders. rendererbackend = "gl" ; Enables additional debug information in renderer backend. @@ -125,6 +126,9 @@ renderer.backend.debuglabels = "false" renderer.backend.debugscopedlabels = "false" +renderer.backend.vulkan.disabledescriptorindexing = "false" +renderer.backend.vulkan.deviceindexoverride = -1 + ; Should not be edited. It's used only for preventing of running fixed pipeline. renderpath = default @@ -332,8 +336,7 @@ unloadturrets = "U" ; Unload turreted units. leaveturret = "U" ; Leave turret point. move = "" ; Modifier to move to a point instead of another action (e.g. gather) -capture = Ctrl ; Modifier to capture instead of another action (e.g. attack) -attack = "" ; Modifier to attack instead of another action (e.g. capture) +attack = Ctrl ; Modifier to attack instead of another action (e.g. capture) attackmove = Ctrl ; Modifier to attackmove when clicking on a point attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point garrison = Ctrl ; Modifier to garrison when clicking on building Index: ps/trunk/binaries/data/mods/mod/shaders/glsl/common/fragment.h =================================================================== --- ps/trunk/binaries/data/mods/mod/shaders/glsl/common/fragment.h +++ ps/trunk/binaries/data/mods/mod/shaders/glsl/common/fragment.h @@ -4,10 +4,32 @@ #include "common/texture.h" #include "common/uniform.h" +#if USE_SPIRV + +#if USE_DESCRIPTOR_INDEXING +#extension GL_EXT_nonuniform_qualifier : enable +const int DESCRIPTOR_INDEXING_SET_SIZE = 16384; +layout (set = 0, binding = 0) uniform sampler2D textures2D[DESCRIPTOR_INDEXING_SET_SIZE]; +layout (set = 0, binding = 1) uniform samplerCube texturesCube[DESCRIPTOR_INDEXING_SET_SIZE]; +layout (set = 0, binding = 2) uniform sampler2DShadow texturesShadow[DESCRIPTOR_INDEXING_SET_SIZE]; +#endif // USE_DESCRIPTOR_INDEXING + +layout (location = 0) out vec4 fragmentColor; + +#define OUTPUT_FRAGMENT_SINGLE_COLOR(COLOR) \ + fragmentColor = COLOR + +#define OUTPUT_FRAGMENT_COLOR(LOCATION, COLOR) \ + gl_FragData[LOCATION] = COLOR + +#else // USE_SPIRV + #define OUTPUT_FRAGMENT_SINGLE_COLOR(COLOR) \ gl_FragColor = COLOR #define OUTPUT_FRAGMENT_COLOR(LOCATION, COLOR) \ gl_FragData[LOCATION] = COLOR +#endif // USE_SPIRV + #endif // INCLUDED_COMMON_FRAGMENT Index: ps/trunk/binaries/data/mods/mod/shaders/glsl/common/stage.h =================================================================== --- ps/trunk/binaries/data/mods/mod/shaders/glsl/common/stage.h +++ ps/trunk/binaries/data/mods/mod/shaders/glsl/common/stage.h @@ -3,7 +3,21 @@ #include "common/uniform.h" +#if USE_SPIRV + +#if STAGE_VERTEX +#define VERTEX_OUTPUT(LOCATION, TYPE, NAME) \ + layout (location = LOCATION) out TYPE NAME +#elif STAGE_FRAGMENT +#define VERTEX_OUTPUT(LOCATION, TYPE, NAME) \ + layout (location = LOCATION) in TYPE NAME +#endif + +#else // USE_SPIRV + #define VERTEX_OUTPUT(LOCATION, TYPE, NAME) \ varying TYPE NAME +#endif // USE_SPIRV + #endif // INCLUDED_COMMON_STAGE Index: ps/trunk/binaries/data/mods/mod/shaders/glsl/common/texture.h =================================================================== --- ps/trunk/binaries/data/mods/mod/shaders/glsl/common/texture.h +++ ps/trunk/binaries/data/mods/mod/shaders/glsl/common/texture.h @@ -1,8 +1,14 @@ #ifndef INCLUDED_COMMON_TEXTURE #define INCLUDED_COMMON_TEXTURE -#define SAMPLE_2D texture2D -#define SAMPLE_2D_SHADOW shadow2D -#define SAMPLE_CUBE textureCube +#if USE_SPIRV + #define SAMPLE_2D texture + #define SAMPLE_2D_SHADOW texture + #define SAMPLE_CUBE texture +#else + #define SAMPLE_2D texture2D + #define SAMPLE_2D_SHADOW shadow2D + #define SAMPLE_CUBE textureCube +#endif #endif // INCLUDED_COMMON_TEXTURE Index: ps/trunk/binaries/data/mods/mod/shaders/glsl/common/uniform.h =================================================================== --- ps/trunk/binaries/data/mods/mod/shaders/glsl/common/uniform.h +++ ps/trunk/binaries/data/mods/mod/shaders/glsl/common/uniform.h @@ -1,6 +1,61 @@ #ifndef INCLUDED_COMMON_UNIFORM #define INCLUDED_COMMON_UNIFORM +#if USE_SPIRV + +#if USE_DESCRIPTOR_INDEXING + #define BEGIN_DRAW_TEXTURES struct DrawTextures { + #define END_DRAW_TEXTURES }; + #define NO_DRAW_TEXTURES uint padding; // We can't have empty struct in GLSL. + + #define TEXTURE_2D(LOCATION, NAME) uint NAME; + #define TEXTURE_2D_SHADOW(LOCATION, NAME) uint NAME; + #define TEXTURE_CUBE(LOCATION, NAME) uint NAME; + #define GET_DRAW_TEXTURE_2D(NAME) \ + textures2D[drawTextures.NAME] + #define GET_DRAW_TEXTURE_2D_SHADOW(NAME) \ + texturesShadow[drawTextures.NAME] + #define GET_DRAW_TEXTURE_CUBE(NAME) \ + texturesCube[drawTextures.NAME] +#else // USE_DESCRIPTOR_INDEXING + #define BEGIN_DRAW_TEXTURES + #define END_DRAW_TEXTURES + #define NO_DRAW_TEXTURES + +#if STAGE_FRAGMENT + #define TEXTURE_2D(LOCATION, NAME) \ + layout (set = 1, binding = LOCATION) uniform sampler2D NAME; + #define TEXTURE_2D_SHADOW(LOCATION, NAME) \ + layout (set = 1, binding = LOCATION) uniform sampler2DShadow NAME; + #define TEXTURE_CUBE(LOCATION, NAME) \ + layout (set = 1, binding = LOCATION) uniform samplerCube NAME; +#else + #define TEXTURE_2D(LOCATION, NAME) + #define TEXTURE_2D_SHADOW(LOCATION, NAME) + #define TEXTURE_CUBE(LOCATION, NAME) +#endif + #define GET_DRAW_TEXTURE_2D(NAME) NAME + #define GET_DRAW_TEXTURE_2D_SHADOW(NAME) NAME + #define GET_DRAW_TEXTURE_CUBE(NAME) NAME +#endif // USE_DESCRIPTOR_INDEXING + +#if USE_DESCRIPTOR_INDEXING + #define BEGIN_DRAW_UNIFORMS layout (push_constant) uniform DrawUniforms { + #define END_DRAW_UNIFORMS DrawTextures drawTextures; }; + #define BEGIN_MATERIAL_UNIFORMS layout (std140, set = 1, binding = 0) uniform MaterialUniforms { + #define END_MATERIAL_UNIFORMS }; +#else + #define BEGIN_DRAW_UNIFORMS layout (push_constant) uniform DrawUniforms { + #define END_DRAW_UNIFORMS }; + #define BEGIN_MATERIAL_UNIFORMS layout (std140, set = 0, binding = 0) uniform MaterialUniforms { + #define END_MATERIAL_UNIFORMS }; +#endif + +#define UNIFORM(TYPE, NAME) \ + TYPE NAME; + +#else // USE_SPIRV + #define BEGIN_DRAW_TEXTURES #define END_DRAW_TEXTURES #define NO_DRAW_TEXTURES @@ -33,4 +88,6 @@ #define UNIFORM(TYPE, NAME) \ uniform TYPE NAME; +#endif // USE_SPIRV + #endif // INCLUDED_COMMON_UNIFORM Index: ps/trunk/binaries/data/mods/mod/shaders/glsl/common/vertex.h =================================================================== --- ps/trunk/binaries/data/mods/mod/shaders/glsl/common/vertex.h +++ ps/trunk/binaries/data/mods/mod/shaders/glsl/common/vertex.h @@ -3,10 +3,27 @@ #include "common/uniform.h" +#if USE_SPIRV + +#define VERTEX_INPUT_ATTRIBUTE(LOCATION, TYPE, NAME) \ + layout (location = LOCATION) in TYPE NAME + +#define OUTPUT_VERTEX_POSITION(POSITION) \ + { \ + vec4 position = (POSITION); \ + position.y = -position.y; \ + position.z = (position.z + position.w) / 2.0; \ + gl_Position = position; \ + } + +#else // USE_SPIRV + #define VERTEX_INPUT_ATTRIBUTE(LOCATION, TYPE, NAME) \ attribute TYPE NAME #define OUTPUT_VERTEX_POSITION(position) \ gl_Position = position +#endif // USE_SPIRV + #endif // INCLUDED_COMMON_VERTEX Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json +++ ps/trunk/binaries/data/mods/public/gui/options/options.json @@ -191,7 +191,8 @@ "config": "rendererbackend", "list": [ { "value": "gl", "label": "OpenGL", "tooltip": "Default OpenGL backend with GLSL. REQUIRES GAME RESTART" }, - { "value": "glarb", "label": "OpenGL ARB", "tooltip": "Legacy OpenGL backend with ARB shaders. REQUIRES GAME RESTART" } + { "value": "glarb", "label": "OpenGL ARB", "tooltip": "Legacy OpenGL backend with ARB shaders. REQUIRES GAME RESTART" }, + { "value": "vulkan", "label": "Vulkan", "tooltip": "Modern API, requires up-to-date drivers. REQUIRES GAME RESTART" } ] }, { Index: ps/trunk/source/graphics/LOSTexture.cpp =================================================================== --- ps/trunk/source/graphics/LOSTexture.cpp +++ ps/trunk/source/graphics/LOSTexture.cpp @@ -175,6 +175,11 @@ viewportRect.height = m_Texture->GetHeight(); deviceCommandContext->SetViewports(1, &viewportRect); + const bool flip = + deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN; + const float bottomV = flip ? 1.0 : 0.0f; + const float topV = flip ? 0.0f : 1.0f; + float quadVerts[] = { 1.0f, 1.0f, @@ -187,13 +192,13 @@ }; float quadTex[] = { - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f + 1.0f, topV, + 0.0f, topV, + 0.0f, bottomV, + + 0.0f, bottomV, + 1.0f, bottomV, + 1.0f, topV }; deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout); Index: ps/trunk/source/graphics/MiniMapTexture.h =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.h +++ ps/trunk/source/graphics/MiniMapTexture.h @@ -58,6 +58,8 @@ const CTexturePtr& GetTexture() const { return m_FinalTexture; } + bool IsFlipped() const { return m_Flipped; } + /** * @return The maximum height for unit passage in water. */ @@ -96,6 +98,7 @@ bool m_TerrainTextureDirty = true; bool m_FinalTextureDirty = true; double m_LastFinalTextureUpdate = 0.0; + bool m_Flipped = false; // minimap texture handles std::unique_ptr m_TerrainTexture; Index: ps/trunk/source/graphics/MiniMapTexture.cpp =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.cpp +++ ps/trunk/source/graphics/MiniMapTexture.cpp @@ -251,8 +251,11 @@ }}; m_QuadVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes); + Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice(); + m_Flipped = device->GetBackend() == Renderer::Backend::Backend::VULKAN; + const uint32_t stride = m_VertexArray.GetStride(); - if (g_VideoMode.GetBackendDevice()->GetCapabilities().instancing) + if (device->GetCapabilities().instancing) { m_UseInstancing = true; Index: ps/trunk/source/graphics/ShaderManager.cpp =================================================================== --- ps/trunk/source/graphics/ShaderManager.cpp +++ ps/trunk/source/graphics/ShaderManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -243,6 +243,11 @@ if (device->GetBackend() != Renderer::Backend::Backend::GL) isUsable = false; } + else if (attrs.GetNamedItem(at_shaders) == "spirv") + { + if (device->GetBackend() != Renderer::Backend::Backend::VULKAN) + isUsable = false; + } else if (!attrs.GetNamedItem(at_context).empty()) { CStr cond = attrs.GetNamedItem(at_context); Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -364,8 +364,10 @@ { const CVector2D center = m_CachedActualSize.CenterPoint(); const CRect source( - 0, miniMapTexture.GetTexture()->GetHeight(), - miniMapTexture.GetTexture()->GetWidth(), 0); + 0, + miniMapTexture.IsFlipped() ? 0 : miniMapTexture.GetTexture()->GetHeight(), + miniMapTexture.GetTexture()->GetWidth(), + miniMapTexture.IsFlipped() ? miniMapTexture.GetTexture()->GetHeight() : 0); const CSize2D size(m_CachedActualSize.GetSize() / m_MapScale); const CRect destination(center - size / 2.0f, size); canvas.DrawRotatedTexture( Index: ps/trunk/source/ps/CStrInternStatic.h =================================================================== --- ps/trunk/source/ps/CStrInternStatic.h +++ ps/trunk/source/ps/CStrInternStatic.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -63,6 +63,7 @@ X(RENDER_DEBUG_MODE_CUSTOM) X(RENDER_DEBUG_MODE_NONE) X(SHADOWS_CASCADE_COUNT) +X(USE_DESCRIPTOR_INDEXING) X(USE_FANCY_EFFECTS) X(USE_FP_SHADOW) X(USE_GPU_INSTANCING) Index: ps/trunk/source/renderer/PostprocManager.cpp =================================================================== --- ps/trunk/source/renderer/PostprocManager.cpp +++ ps/trunk/source/renderer/PostprocManager.cpp @@ -38,6 +38,50 @@ #include +namespace +{ + +void DrawFullscreenQuad( + Renderer::Backend::IVertexInputLayout* vertexInputLayout, + Renderer::Backend::IDeviceCommandContext* deviceCommandContext) +{ + float quadVerts[] = + { + 1.0f, 1.0f, + -1.0f, 1.0f, + -1.0f, -1.0f, + + -1.0f, -1.0f, + 1.0f, -1.0f, + 1.0f, 1.0f + }; + const bool flip = + deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN; + const float bottomV = flip ? 1.0 : 0.0f; + const float topV = flip ? 0.0f : 1.0f; + float quadTex[] = + { + 1.0f, topV, + 0.0f, topV, + 0.0f, bottomV, + + 0.0f, bottomV, + 1.0f, bottomV, + 1.0f, topV + }; + + deviceCommandContext->SetVertexInputLayout(vertexInputLayout); + + deviceCommandContext->SetVertexBufferData( + 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); + deviceCommandContext->SetVertexBufferData( + 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); + + deviceCommandContext->Draw(0, 6); +} + +} // anonymous namespace + CPostprocManager::CPostprocManager() : m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0) @@ -140,7 +184,9 @@ name = backendDevice->CreateTexture2D( \ "PostProc" #name, \ Renderer::Backend::ITexture::Usage::SAMPLED | \ - Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT, \ + Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT | \ + Renderer::Backend::ITexture::Usage::TRANSFER_SRC | \ + Renderer::Backend::ITexture::Usage::TRANSFER_DST, \ Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \ Renderer::Backend::Sampler::MakeDefaultSampler( \ Renderer::Backend::Sampler::Filter::LINEAR, \ @@ -247,36 +293,7 @@ deviceCommandContext->SetTexture( shader->GetBindingSlot(str_renderedTex), inTex); - // TODO: remove the fullscreen quad drawing duplication. - float quadVerts[] = - { - 1.0f, 1.0f, - -1.0f, 1.0f, - -1.0f, -1.0f, - - -1.0f, -1.0f, - 1.0f, -1.0f, - 1.0f, 1.0f - }; - float quadTex[] = - { - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f - }; - - deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout); - - deviceCommandContext->SetVertexBufferData( - 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); - deviceCommandContext->SetVertexBufferData( - 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); - - deviceCommandContext->Draw(0, 6); + DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext); deviceCommandContext->EndPass(); deviceCommandContext->EndFramebufferPass(); @@ -311,35 +328,7 @@ deviceCommandContext->SetUniform( shader->GetBindingSlot(str_texSize), inWidth, inHeight); - float quadVerts[] = - { - 1.0f, 1.0f, - -1.0f, 1.0f, - -1.0f, -1.0f, - - -1.0f, -1.0f, - 1.0f, -1.0f, - 1.0f, 1.0f - }; - float quadTex[] = - { - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f - }; - - deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout); - - deviceCommandContext->SetVertexBufferData( - 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); - deviceCommandContext->SetVertexBufferData( - 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); - - deviceCommandContext->Draw(0, 6); + DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext); deviceCommandContext->EndPass(); deviceCommandContext->EndFramebufferPass(); @@ -364,14 +353,7 @@ deviceCommandContext->SetUniform( shader->GetBindingSlot(str_texSize), inWidth, inHeight); - deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout); - - deviceCommandContext->SetVertexBufferData( - 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); - deviceCommandContext->SetVertexBufferData( - 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); - - deviceCommandContext->Draw(0, 6); + DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext); deviceCommandContext->EndPass(); deviceCommandContext->EndFramebufferPass(); @@ -466,35 +448,7 @@ deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation); deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom); - float quadVerts[] = - { - 1.0f, 1.0f, - -1.0f, 1.0f, - -1.0f, -1.0f, - - -1.0f, -1.0f, - 1.0f, -1.0f, - 1.0f, 1.0f - }; - float quadTex[] = - { - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f - }; - - deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout); - - deviceCommandContext->SetVertexBufferData( - 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0])); - deviceCommandContext->SetVertexBufferData( - 1, quadTex, std::size(quadTex) * sizeof(quadTex[0])); - - deviceCommandContext->Draw(0, 6); + DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext); deviceCommandContext->EndPass(); deviceCommandContext->EndFramebufferPass(); @@ -663,7 +617,8 @@ m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS", Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE, - Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT, + Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT | + Renderer::Backend::ITexture::Usage::TRANSFER_SRC, Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, @@ -672,7 +627,8 @@ // Allocate the Depth/Stencil texture. m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS", Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE, - Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT, + Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT | + Renderer::Backend::ITexture::Usage::TRANSFER_SRC, Renderer::Backend::Format::D24_S8, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Index: ps/trunk/source/renderer/RenderingOptions.cpp =================================================================== --- ps/trunk/source/renderer/RenderingOptions.cpp +++ ps/trunk/source/renderer/RenderingOptions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -240,10 +240,15 @@ m_ConfigHooks->Setup("gpuskinning", [this]() { bool enabled; CFG_GET_VAL("gpuskinning", enabled); - if (enabled && g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB) - LOGWARNING("GPUSkinning has been disabled, because it is not supported with ARB shaders."); - else if (enabled) - m_GPUSkinning = true; + if (enabled) + { + if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB) + LOGWARNING("GPUSkinning has been disabled, because it is not supported with ARB shaders."); + else if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN) + LOGWARNING("GPUSkinning has been disabled, because it is not supported for Vulkan backend yet."); + else + m_GPUSkinning = true; + } }); m_ConfigHooks->Setup("renderactors", m_RenderActors); Index: ps/trunk/source/renderer/SceneRenderer.cpp =================================================================== --- ps/trunk/source/renderer/SceneRenderer.cpp +++ ps/trunk/source/renderer/SceneRenderer.cpp @@ -567,6 +567,13 @@ // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); + if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN) + { + CMatrix3D flip; + flip.SetIdentity(); + flip._22 = -1.0f; + wm.m_ReflectionMatrix = flip * wm.m_ReflectionMatrix; + } float vpHeight = wm.m_RefTextureSize; float vpWidth = wm.m_RefTextureSize; @@ -643,6 +650,15 @@ wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse(); wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation(); + if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN) + { + CMatrix3D flip; + flip.SetIdentity(); + flip._22 = -1.0f; + wm.m_RefractionMatrix = flip * wm.m_RefractionMatrix; + wm.m_RefractionProjInvMatrix = wm.m_RefractionProjInvMatrix * flip; + } + float vpHeight = wm.m_RefTextureSize; float vpWidth = wm.m_RefTextureSize; @@ -702,6 +718,7 @@ // inverted depth test so any behind an occluder will get drawn in a constant // color. + // TODO: do we need clear here? deviceCommandContext->ClearFramebuffer(false, true, true); // Render occluders: Index: ps/trunk/source/renderer/ShadowMap.cpp =================================================================== --- ps/trunk/source/renderer/ShadowMap.cpp +++ ps/trunk/source/renderer/ShadowMap.cpp @@ -460,6 +460,15 @@ lightToTex._34 = -shadowRenderBound[0].Z * texscalez; lightToTex._44 = 1.0; + if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN) + { + CMatrix3D flip; + flip.SetIdentity(); + flip._22 = -1.0f; + flip._24 = 1.0; + lightToTex = flip * lightToTex; + } + Cascades[cascade].TextureMatrix = lightToTex * LightTransform; } Index: ps/trunk/source/renderer/WaterManager.cpp =================================================================== --- ps/trunk/source/renderer/WaterManager.cpp +++ ps/trunk/source/renderer/WaterManager.cpp @@ -57,10 +57,7 @@ CVector3D m_RetreatPosition; CVector2D m_PerpVect; - u8 m_UV[3]; - - // pad to a power of two - u8 m_Padding[5]; + float m_UV[2]; }; cassert(sizeof(SWavesVertex) == 64); @@ -143,7 +140,7 @@ offsetof(SWavesVertex, m_PerpVect), stride, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}, {Renderer::Backend::VertexAttributeStream::UV0, - Renderer::Backend::Format::R8G8_UINT, + Renderer::Backend::Format::R32G32_SFLOAT, offsetof(SWavesVertex, m_UV), stride, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}, Index: ps/trunk/source/renderer/backend/Format.h =================================================================== --- ps/trunk/source/renderer/backend/Format.h +++ ps/trunk/source/renderer/backend/Format.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -62,6 +62,15 @@ BC3_UNORM }; +inline bool IsDepthFormat(const Format format) +{ + return + format == Format::D16 || + format == Format::D24 || + format == Format::D24_S8 || + format == Format::D32; +} + } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/IBuffer.h =================================================================== --- ps/trunk/source/renderer/backend/IBuffer.h +++ ps/trunk/source/renderer/backend/IBuffer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -34,7 +34,9 @@ enum class Type { VERTEX, - INDEX + INDEX, + UPLOAD, + UNIFORM, }; virtual Type GetType() const = 0; Index: ps/trunk/source/renderer/backend/gl/Buffer.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Buffer.cpp +++ ps/trunk/source/renderer/backend/gl/Buffer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -40,6 +40,7 @@ CDevice* device, const char* name, const Type type, const uint32_t size, const bool dynamic) { + ENSURE(type == Type::VERTEX || type == Type::INDEX); std::unique_ptr buffer(new CBuffer()); buffer->m_Device = device; buffer->m_Type = type; Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp +++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp @@ -103,6 +103,10 @@ case CBuffer::Type::INDEX: target = GL_ELEMENT_ARRAY_BUFFER; break; + case CBuffer::Type::UPLOAD: + case CBuffer::Type::UNIFORM: + debug_warn("Unsupported buffer type."); + break; }; return target; } Index: ps/trunk/source/renderer/backend/vulkan/Buffer.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Buffer.h +++ ps/trunk/source/renderer/backend/vulkan/Buffer.h @@ -0,0 +1,82 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_BUFFER +#define INCLUDED_RENDERER_BACKEND_VULKAN_BUFFER + +#include "renderer/backend/IBuffer.h" +#include "renderer/backend/vulkan/VMA.h" + +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; + +class CBuffer final : public IBuffer +{ +public: + ~CBuffer() override; + + IDevice* GetDevice() override; + + Type GetType() const override { return m_Type; } + uint32_t GetSize() const override { return m_Size; } + bool IsDynamic() const override { return m_Dynamic; } + + VkBuffer GetVkBuffer() { return m_Buffer; } + + /** + * @return mapped data for UPLOAD buffers else returns nullptr. + */ + void* GetMappedData() { return m_AllocationInfo.pMappedData; } + +private: + friend class CDevice; + + static std::unique_ptr Create( + CDevice* device, const char* name, const Type type, const uint32_t size, + const bool dynamic); + + CBuffer(); + + CDevice* m_Device = nullptr; + + Type m_Type = Type::VERTEX; + uint32_t m_Size = 0; + bool m_Dynamic = false; + + VkBuffer m_Buffer = VK_NULL_HANDLE; + VmaAllocation m_Allocation = VK_NULL_HANDLE; + VmaAllocationInfo m_AllocationInfo{}; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_BUFFER Index: ps/trunk/source/renderer/backend/vulkan/Buffer.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Buffer.cpp +++ ps/trunk/source/renderer/backend/vulkan/Buffer.cpp @@ -0,0 +1,117 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "Buffer.h" + +#include "renderer/backend/vulkan/Device.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +// static +std::unique_ptr CBuffer::Create( + CDevice* device, const char* name, const Type type, const uint32_t size, + const bool dynamic) +{ + std::unique_ptr buffer(new CBuffer()); + buffer->m_Device = device; + buffer->m_Type = type; + buffer->m_Size = size; + buffer->m_Dynamic = dynamic; + + VkMemoryPropertyFlags properties = 0; + VkBufferUsageFlags usage = VK_BUFFER_USAGE_FLAG_BITS_MAX_ENUM; + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO; + switch (type) + { + case Type::VERTEX: + usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + break; + case Type::INDEX: + usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + break; + case Type::UPLOAD: + usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + break; + case Type::UNIFORM: + usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + break; + } + + VkBufferCreateInfo bufferCreateInfo{}; + bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferCreateInfo.size = size; + bufferCreateInfo.usage = usage; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocationCreateInfo{}; + if (type == Type::UPLOAD) + allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; +#ifndef NDEBUG + allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT; + allocationCreateInfo.pUserData = const_cast(name); +#endif + allocationCreateInfo.requiredFlags = properties; + allocationCreateInfo.usage = memoryUsage; + const VkResult createBufferResult = vmaCreateBuffer( + device->GetVMAAllocator(), &bufferCreateInfo, &allocationCreateInfo, + &buffer->m_Buffer, &buffer->m_Allocation, &buffer->m_AllocationInfo); + if (createBufferResult != VK_SUCCESS) + { + LOGERROR("Failed to create VkBuffer: %d", static_cast(createBufferResult)); + return nullptr; + } + + device->SetObjectName(VK_OBJECT_TYPE_BUFFER, buffer->m_Buffer, name); + + return buffer; +} + +CBuffer::CBuffer() = default; + +CBuffer::~CBuffer() +{ + if (m_Allocation != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_BUFFER, m_Buffer, m_Allocation); +} + +IDevice* CBuffer::GetDevice() +{ + return m_Device; +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/DescriptorManager.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DescriptorManager.h +++ ps/trunk/source/renderer/backend/vulkan/DescriptorManager.h @@ -0,0 +1,124 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DESCRIPTORMANAGER +#define INCLUDED_RENDERER_BACKEND_VULKAN_DESCRIPTORMANAGER + +#include "renderer/backend/Sampler.h" +#include "renderer/backend/vulkan/Texture.h" + +#include +#include +#include +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; + +class CDescriptorManager +{ +public: + CDescriptorManager(CDevice* device, const bool useDescriptorIndexing); + ~CDescriptorManager(); + + bool UseDescriptorIndexing() const { return m_UseDescriptorIndexing; } + + /** + * @return a single type descriptor set layout with the number of bindings + * equals to the size. The returned layout is owned by the manager. + */ + VkDescriptorSetLayout GetSingleTypeDescritorSetLayout( + VkDescriptorType type, const uint32_t size); + + VkDescriptorSet GetSingleTypeDescritorSet( + VkDescriptorType type, VkDescriptorSetLayout layout, + const std::vector& texturesUID, + const std::vector& textures); + + uint32_t GetUniformSet() const; + + uint32_t GetTextureDescriptor(CTexture* texture); + void OnTextureDestroy(const CTexture::UID uid); + + const VkDescriptorSetLayout& GetDescriptorIndexingSetLayout() const { return m_DescriptorIndexingSetLayout; } + const VkDescriptorSetLayout& GetUniformDescriptorSetLayout() const { return m_UniformDescriptorSetLayout; } + const VkDescriptorSet& GetDescriptorIndexingSet() { return m_DescriptorIndexingSet; } + + const std::vector& GetDescriptorSetLayouts() const { return m_DescriptorSetLayouts; } + +private: + struct SingleTypePool + { + VkDescriptorSetLayout layout; + VkDescriptorPool pool; + int16_t firstFreeIndex = 0; + std::vector> elements; + }; + SingleTypePool& GetSingleTypePool(const VkDescriptorType type, const uint32_t size); + + CDevice* m_Device = nullptr; + + bool m_UseDescriptorIndexing = false; + + VkDescriptorPool m_DescriptorIndexingPool = VK_NULL_HANDLE; + VkDescriptorSet m_DescriptorIndexingSet = VK_NULL_HANDLE; + VkDescriptorSetLayout m_DescriptorIndexingSetLayout = VK_NULL_HANDLE; + VkDescriptorSetLayout m_UniformDescriptorSetLayout = VK_NULL_HANDLE; + std::vector m_DescriptorSetLayouts; + + static constexpr uint32_t DESCRIPTOR_INDEXING_BINDING_SIZE = 16384; + static constexpr uint32_t NUMBER_OF_BINDINGS_PER_DESCRIPTOR_INDEXING_SET = 3; + + struct DescriptorIndexingBindingMap + { + static_assert(std::numeric_limits::max() >= DESCRIPTOR_INDEXING_BINDING_SIZE); + int16_t firstFreeIndex = 0; + std::vector elements; + std::unordered_map map; + }; + std::array + m_DescriptorIndexingBindings; + std::unordered_map m_TextureToBindingMap; + + std::unordered_map> m_SingleTypePools; + std::unordered_map>> m_TextureSingleTypePoolMap; + + using SingleTypeCacheKey = std::pair>; + struct SingleTypeCacheKeyHash + { + size_t operator()(const SingleTypeCacheKey& key) const; + }; + std::unordered_map m_SingleTypeSets; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_DESCRIPTORMANAGER Index: ps/trunk/source/renderer/backend/vulkan/DescriptorManager.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DescriptorManager.cpp +++ ps/trunk/source/renderer/backend/vulkan/DescriptorManager.cpp @@ -0,0 +1,365 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "DescriptorManager.h" + +#include "lib/hash.h" +#include "ps/containers/StaticVector.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/Utilities.h" + +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +CDescriptorManager::CDescriptorManager(CDevice* device, const bool useDescriptorIndexing) + : m_Device(device), m_UseDescriptorIndexing(useDescriptorIndexing) +{ + if (useDescriptorIndexing) + { + VkDescriptorPoolSize descriptorPoolSize{}; + descriptorPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorPoolSize.descriptorCount = DESCRIPTOR_INDEXING_BINDING_SIZE * NUMBER_OF_BINDINGS_PER_DESCRIPTOR_INDEXING_SET; + + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{}; + descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCreateInfo.poolSizeCount = 1; + descriptorPoolCreateInfo.pPoolSizes = &descriptorPoolSize; + descriptorPoolCreateInfo.maxSets = 1; + descriptorPoolCreateInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT; + ENSURE_VK_SUCCESS(vkCreateDescriptorPool( + device->GetVkDevice(), &descriptorPoolCreateInfo, nullptr, &m_DescriptorIndexingPool)); + + const VkShaderStageFlags stageFlags = + VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; + const std::array bindings{{ + {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESCRIPTOR_INDEXING_BINDING_SIZE, stageFlags}, + {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESCRIPTOR_INDEXING_BINDING_SIZE, stageFlags}, + {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESCRIPTOR_INDEXING_BINDING_SIZE, stageFlags} + }}; + + const VkDescriptorBindingFlagsEXT baseBindingFlags = + VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT + | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT + | VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT; + const std::array bindingFlags{{ + baseBindingFlags, baseBindingFlags, baseBindingFlags + }}; + + VkDescriptorSetLayoutBindingFlagsCreateInfoEXT bindingFlagsCreateInfo{}; + bindingFlagsCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT; + bindingFlagsCreateInfo.bindingCount = bindingFlags.size(); + bindingFlagsCreateInfo.pBindingFlags = bindingFlags.data(); + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{}; + descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorSetLayoutCreateInfo.bindingCount = bindings.size(); + descriptorSetLayoutCreateInfo.pBindings = bindings.data(); + descriptorSetLayoutCreateInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT; + descriptorSetLayoutCreateInfo.pNext = &bindingFlagsCreateInfo; + + ENSURE_VK_SUCCESS(vkCreateDescriptorSetLayout( + device->GetVkDevice(), &descriptorSetLayoutCreateInfo, + nullptr, &m_DescriptorIndexingSetLayout)); + + m_DescriptorSetLayouts.emplace_back(m_DescriptorIndexingSetLayout); + + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{}; + descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocateInfo.descriptorPool = m_DescriptorIndexingPool; + descriptorSetAllocateInfo.descriptorSetCount = 1; + descriptorSetAllocateInfo.pSetLayouts = &m_DescriptorIndexingSetLayout; + + ENSURE_VK_SUCCESS(vkAllocateDescriptorSets( + device->GetVkDevice(), &descriptorSetAllocateInfo, &m_DescriptorIndexingSet)); + + for (DescriptorIndexingBindingMap& bindingMap : m_DescriptorIndexingBindings) + { + bindingMap.firstFreeIndex = 0; + bindingMap.elements.resize(DESCRIPTOR_INDEXING_BINDING_SIZE); + std::iota(bindingMap.elements.begin(), std::prev(bindingMap.elements.end()), 1); + bindingMap.elements.back() = -1; + } + } + + // Currently we hard-code the layout for uniforms. + const VkDescriptorSetLayoutBinding bindings[] = + { + {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT} + }; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{}; + descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorSetLayoutCreateInfo.bindingCount = std::size(bindings); + descriptorSetLayoutCreateInfo.pBindings = bindings; + + ENSURE_VK_SUCCESS(vkCreateDescriptorSetLayout( + device->GetVkDevice(), &descriptorSetLayoutCreateInfo, + nullptr, &m_UniformDescriptorSetLayout)); + m_DescriptorSetLayouts.emplace_back(m_UniformDescriptorSetLayout); +} + +CDescriptorManager::~CDescriptorManager() +{ + VkDevice device = m_Device->GetVkDevice(); + + for (auto& pair: m_SingleTypePools) + { + for (SingleTypePool& pool : pair.second) + { + if (pool.pool != VK_NULL_HANDLE) + vkDestroyDescriptorPool(device, pool.pool, nullptr); + if (pool.layout != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(device, pool.layout, nullptr); + } + } + m_SingleTypePools.clear(); + + for (VkDescriptorSetLayout descriptorSetLayout : m_DescriptorSetLayouts) + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + m_DescriptorSetLayouts.clear(); + + if (m_DescriptorIndexingPool != VK_NULL_HANDLE) + vkDestroyDescriptorPool(device, m_DescriptorIndexingPool, nullptr); +} + +CDescriptorManager::SingleTypePool& CDescriptorManager::GetSingleTypePool( + const VkDescriptorType type, const uint32_t size) +{ + ENSURE(size > 0 && size <= 16); + std::vector& pools = m_SingleTypePools[type]; + if (pools.size() <= size) + pools.resize(size + 1); + SingleTypePool& pool = pools[size]; + if (pool.pool == VK_NULL_HANDLE) + { + constexpr uint32_t maxSets = 16384; + + VkDescriptorPoolSize descriptorPoolSize{}; + descriptorPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorPoolSize.descriptorCount = maxSets * size; + + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{}; + descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCreateInfo.poolSizeCount = 1; + descriptorPoolCreateInfo.pPoolSizes = &descriptorPoolSize; + descriptorPoolCreateInfo.maxSets = maxSets; + ENSURE_VK_SUCCESS(vkCreateDescriptorPool( + m_Device->GetVkDevice(), &descriptorPoolCreateInfo, nullptr, &pool.pool)); + + const VkPipelineStageFlags stageFlags = + VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; + PS::StaticVector bindings; + for (uint32_t index = 0; index < size; ++index) + bindings.emplace_back(VkDescriptorSetLayoutBinding{index, type, 1, stageFlags}); + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{}; + descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorSetLayoutCreateInfo.bindingCount = size; + descriptorSetLayoutCreateInfo.pBindings = bindings.data(); + + ENSURE_VK_SUCCESS(vkCreateDescriptorSetLayout( + m_Device->GetVkDevice(), &descriptorSetLayoutCreateInfo, nullptr, &pool.layout)); + + pool.firstFreeIndex = 0; + pool.elements.reserve(maxSets); + for (uint32_t index = 0; index < maxSets; ++index) + pool.elements.push_back({VK_NULL_HANDLE, static_cast(index + 1)}); + pool.elements.back().second = -1; + } + return pool; +} + +VkDescriptorSetLayout CDescriptorManager::GetSingleTypeDescritorSetLayout( + VkDescriptorType type, const uint32_t size) +{ + return GetSingleTypePool(type, size).layout; +} + +size_t CDescriptorManager::SingleTypeCacheKeyHash::operator()(const SingleTypeCacheKey& key) const +{ + size_t seed = 0; + hash_combine(seed, key.first); + for (CTexture::UID uid : key.second) + hash_combine(seed, uid); + return seed; +} + +VkDescriptorSet CDescriptorManager::GetSingleTypeDescritorSet( + VkDescriptorType type, VkDescriptorSetLayout layout, + const std::vector& texturesUID, + const std::vector& textures) +{ + ENSURE(texturesUID.size() == textures.size()); + ENSURE(!texturesUID.empty()); + const SingleTypeCacheKey key{layout, texturesUID}; + auto it = m_SingleTypeSets.find(key); + if (it == m_SingleTypeSets.end()) + { + SingleTypePool& pool = GetSingleTypePool(type, texturesUID.size()); + const int16_t elementIndex = pool.firstFreeIndex; + ENSURE(elementIndex != -1); + std::pair& element = pool.elements[elementIndex]; + pool.firstFreeIndex = element.second; + + if (element.first == VK_NULL_HANDLE) + { + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{}; + descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocateInfo.descriptorPool = pool.pool; + descriptorSetAllocateInfo.descriptorSetCount = 1; + descriptorSetAllocateInfo.pSetLayouts = &layout; + + ENSURE_VK_SUCCESS(vkAllocateDescriptorSets( + m_Device->GetVkDevice(), &descriptorSetAllocateInfo, &element.first)); + } + + it = m_SingleTypeSets.emplace(key, element.first).first; + + for (const CTexture::UID uid : texturesUID) + m_TextureSingleTypePoolMap[uid].push_back({type, static_cast(texturesUID.size()), elementIndex}); + + PS::StaticVector infos; + PS::StaticVector writes; + for (size_t index = 0; index < textures.size(); ++index) + { + if (!textures[index]) + continue; + VkDescriptorImageInfo descriptorImageInfo{}; + descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + descriptorImageInfo.imageView = textures[index]->GetSamplerImageView(); + descriptorImageInfo.sampler = textures[index]->GetSampler(); + infos.emplace_back(std::move(descriptorImageInfo)); + + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.dstSet = element.first; + writeDescriptorSet.dstBinding = index; + writeDescriptorSet.dstArrayElement = 0; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.pImageInfo = &infos.back(); + writes.emplace_back(std::move(writeDescriptorSet)); + } + + vkUpdateDescriptorSets( + m_Device->GetVkDevice(), writes.size(), writes.data(), 0, nullptr); + } + return it->second; +} + +uint32_t CDescriptorManager::GetUniformSet() const +{ + return m_UseDescriptorIndexing ? 1 : 0; +} + +uint32_t CDescriptorManager::GetTextureDescriptor(CTexture* texture) +{ + ENSURE(m_UseDescriptorIndexing); + + uint32_t binding = 0; + if (texture->GetType() == ITexture::Type::TEXTURE_2D && + (texture->GetFormat() == Format::D16 || + texture->GetFormat() == Format::D24 || + texture->GetFormat() == Format::D32 || + texture->GetFormat() == Format::D24_S8) && + texture->IsCompareEnabled()) + binding = 2; + else if (texture->GetType() == ITexture::Type::TEXTURE_CUBE) + binding = 1; + + DescriptorIndexingBindingMap& bindingMap = m_DescriptorIndexingBindings[binding]; + auto it = bindingMap.map.find(texture->GetUID()); + if (it != bindingMap.map.end()) + return it->second; + m_TextureToBindingMap[texture->GetUID()] = binding; + + ENSURE(bindingMap.firstFreeIndex != -1); + uint32_t descriptorSetIndex = bindingMap.firstFreeIndex; + bindingMap.firstFreeIndex = bindingMap.elements[bindingMap.firstFreeIndex]; + + ENSURE(texture->GetType() != ITexture::Type::TEXTURE_2D_MULTISAMPLE); + + VkDescriptorImageInfo descriptorImageInfo{}; + descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + descriptorImageInfo.imageView = texture->GetSamplerImageView(); + descriptorImageInfo.sampler = texture->GetSampler(); + + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.dstSet = m_DescriptorIndexingSet; + writeDescriptorSet.dstBinding = binding; + writeDescriptorSet.dstArrayElement = descriptorSetIndex; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.pImageInfo = &descriptorImageInfo; + + vkUpdateDescriptorSets( + m_Device->GetVkDevice(), 1, &writeDescriptorSet, 0, nullptr); + + bindingMap.map[texture->GetUID()] = descriptorSetIndex; + + return descriptorSetIndex; +} + +void CDescriptorManager::OnTextureDestroy(const CTexture::UID uid) +{ + if (m_UseDescriptorIndexing) + { + DescriptorIndexingBindingMap& bindingMap = + m_DescriptorIndexingBindings[m_TextureToBindingMap[uid]]; + auto it = bindingMap.map.find(uid); + // It's possible to not have the texture in the map. Because a texture will + // be added to it only in case of usage. + if (it == bindingMap.map.end()) + return; + const int16_t index = it->second; + bindingMap.elements[index] = bindingMap.firstFreeIndex; + bindingMap.firstFreeIndex = index; + } + else + { + auto it = m_TextureSingleTypePoolMap.find(uid); + if (it == m_TextureSingleTypePoolMap.end()) + return; + for (const auto& entry : it->second) + { + SingleTypePool& pool = GetSingleTypePool(std::get<0>(entry), std::get<1>(entry)); + const int16_t elementIndex = std::get<2>(entry); + pool.elements[elementIndex].second = pool.firstFreeIndex; + pool.firstFreeIndex = elementIndex; + } + } +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Device.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.h +++ ps/trunk/source/renderer/backend/vulkan/Device.h @@ -20,9 +20,19 @@ #include "renderer/backend/IDevice.h" #include "renderer/backend/vulkan/DeviceForward.h" +#include "renderer/backend/vulkan/DeviceSelection.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/VMA.h" #include "scriptinterface/ScriptForward.h" +#include #include +#include +#include +#include +#include +#include +#include typedef struct SDL_Window SDL_Window; @@ -35,7 +45,18 @@ namespace Vulkan { -class CDevice : public IDevice +static constexpr size_t NUMBER_OF_FRAMES_IN_FLIGHT = 3; + +class CBuffer; +class CDescriptorManager; +class CFramebuffer; +class CRenderPassManager; +class CRingCommandContext; +class CSamplerManager; +class CSubmitScheduler; +class CSwapChain; + +class CDevice final : public IDevice { public: /** @@ -79,6 +100,9 @@ std::unique_ptr CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override; + std::unique_ptr CreateCBuffer( + const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic); + std::unique_ptr CreateShaderProgram( const CStr& name, const CShaderDefines& defines) override; @@ -103,15 +127,88 @@ const Capabilities& GetCapabilities() const override { return m_Capabilities; } + VkDevice GetVkDevice() { return m_Device; } + + VmaAllocator GetVMAAllocator() { return m_VMAAllocator; } + + void ScheduleObjectToDestroy( + VkObjectType type, const void* handle, const VmaAllocation allocation) + { + ScheduleObjectToDestroy(type, reinterpret_cast(handle), allocation); + } + + void ScheduleObjectToDestroy( + VkObjectType type, const uint64_t handle, const VmaAllocation allocation); + + void ScheduleTextureToDestroy(const CTexture::UID uid); + + void SetObjectName(VkObjectType type, const void* handle, const char* name) + { + SetObjectName(type, reinterpret_cast(handle), name); + } + + void SetObjectName(VkObjectType type, const uint64_t handle, const char* name); + + std::unique_ptr CreateRingCommandContext(const size_t size); + + const SAvailablePhysicalDevice& GetChoosenPhysicalDevice() const { return m_ChoosenDevice; } + + CRenderPassManager& GetRenderPassManager() { return *m_RenderPassManager; } + + CSamplerManager& GetSamplerManager() { return *m_SamplerManager; } + + CDescriptorManager& GetDescriptorManager() { return *m_DescriptorManager; } + private: CDevice(); + void RecreateSwapChain(); + bool IsSwapChainValid(); + void ProcessObjectToDestroyQueue(const bool ignoreFrameID = false); + void ProcessTextureToDestroyQueue(const bool ignoreFrameID = false); + std::string m_Name; std::string m_Version; + std::string m_VendorID; std::string m_DriverInformation; std::vector m_Extensions; + std::vector m_InstanceExtensions; + std::vector m_ValidationLayers; + + SAvailablePhysicalDevice m_ChoosenDevice{}; + std::vector m_AvailablePhysicalDevices; Capabilities m_Capabilities{}; + + VkInstance m_Instance = VK_NULL_HANDLE; + + VkDebugUtilsMessengerEXT m_DebugMessenger = VK_NULL_HANDLE; + + SDL_Window* m_Window = nullptr; + VkSurfaceKHR m_Surface = VK_NULL_HANDLE; + VkDevice m_Device = VK_NULL_HANDLE; + VmaAllocator m_VMAAllocator = VK_NULL_HANDLE; + VkQueue m_GraphicsQueue = VK_NULL_HANDLE; + uint32_t m_GraphicsQueueFamilyIndex = std::numeric_limits::max(); + + std::unique_ptr m_SwapChain; + + uint32_t m_FrameID = 0; + + struct ObjectToDestroy + { + uint32_t frameID; + VkObjectType type; + uint64_t handle; + VmaAllocation allocation; + }; + std::queue m_ObjectToDestroyQueue; + std::queue> m_TextureToDestroyQueue; + + std::unique_ptr m_RenderPassManager; + std::unique_ptr m_SamplerManager; + std::unique_ptr m_DescriptorManager; + std::unique_ptr m_SubmitScheduler; }; } // namespace Vulkan Index: ps/trunk/source/renderer/backend/vulkan/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.cpp +++ ps/trunk/source/renderer/backend/vulkan/Device.cpp @@ -20,12 +20,42 @@ #include "Device.h" #include "lib/external_libraries/libsdl.h" +#include "lib/hash.h" +#include "maths/MathUtil.h" +#include "ps/CLogger.h" +#include "ps/ConfigDB.h" +#include "ps/Profile.h" +#include "renderer/backend/vulkan/Buffer.h" +#include "renderer/backend/vulkan/DescriptorManager.h" +#include "renderer/backend/vulkan/DeviceCommandContext.h" +#include "renderer/backend/vulkan/DeviceSelection.h" +#include "renderer/backend/vulkan/Framebuffer.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/PipelineState.h" +#include "renderer/backend/vulkan/RenderPassManager.h" +#include "renderer/backend/vulkan/RingCommandContext.h" +#include "renderer/backend/vulkan/SamplerManager.h" +#include "renderer/backend/vulkan/ShaderProgram.h" +#include "renderer/backend/vulkan/SubmitScheduler.h" +#include "renderer/backend/vulkan/SwapChain.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/Utilities.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" -#if SDL_VERSION_ATLEAST(2, 0, 8) +#include +#include +#include +#include +#include +#include +#include + +// According to https://wiki.libsdl.org/SDL_Vulkan_LoadLibrary the following +// functionality is supported since SDL 2.0.6. +#if SDL_VERSION_ATLEAST(2, 0, 6) #include #endif @@ -38,59 +68,620 @@ namespace Vulkan { +namespace +{ + +std::vector GetRequiredSDLExtensions(SDL_Window* window) +{ + if (!window) + return {}; + + const size_t MAX_EXTENSION_COUNT = 16; + unsigned int SDLExtensionCount = MAX_EXTENSION_COUNT; + const char* SDLExtensions[MAX_EXTENSION_COUNT]; + ENSURE(SDL_Vulkan_GetInstanceExtensions(window, &SDLExtensionCount, SDLExtensions)); + std::vector requiredExtensions; + requiredExtensions.reserve(SDLExtensionCount); + std::copy_n(SDLExtensions, SDLExtensionCount, std::back_inserter(requiredExtensions)); + return requiredExtensions; +} + +std::vector GetAvailableValidationLayers() +{ + uint32_t layerCount = 0; + ENSURE_VK_SUCCESS(vkEnumerateInstanceLayerProperties(&layerCount, nullptr)); + + std::vector availableLayers(layerCount); + ENSURE_VK_SUCCESS(vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data())); + + for (const VkLayerProperties& layer : availableLayers) + { + LOGMESSAGE("Vulkan validation layer: '%s' (%s) v%u.%u.%u.%u", + layer.layerName, layer.description, + VK_API_VERSION_VARIANT(layer.specVersion), + VK_API_VERSION_MAJOR(layer.specVersion), + VK_API_VERSION_MINOR(layer.specVersion), + VK_API_VERSION_PATCH(layer.specVersion)); + } + + std::vector availableValidationLayers; + availableValidationLayers.reserve(layerCount); + for (const VkLayerProperties& layer : availableLayers) + availableValidationLayers.emplace_back(layer.layerName); + return availableValidationLayers; +} + +std::vector GetAvailableInstanceExtensions(const char* layerName = nullptr) +{ + uint32_t extensionCount = 0; + ENSURE_VK_SUCCESS(vkEnumerateInstanceExtensionProperties(layerName, &extensionCount, nullptr)); + std::vector extensions(extensionCount); + ENSURE_VK_SUCCESS(vkEnumerateInstanceExtensionProperties(layerName, &extensionCount, extensions.data())); + + std::vector availableExtensions; + for (const VkExtensionProperties& extension : extensions) + availableExtensions.emplace_back(extension.extensionName); + return availableExtensions; +} + +VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* callbackData, + void* UNUSED(userData)) +{ + if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) || (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)) + LOGMESSAGE("Vulkan: %s", callbackData->pMessage); + else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + { + struct HideRule + { + VkDebugUtilsMessageTypeFlagsEXT flags; + std::string_view pattern; + bool skip; + }; + constexpr HideRule hideRules[] = + { + // Not consumed shader output is a known problem which produces too + // many warning. + {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, "OutputNotConsumed", false}, + // TODO: check vkGetImageMemoryRequirements2 for prefersDedicatedAllocation. + {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "vkBindMemory-small-dedicated-allocation", false}, + {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "vkAllocateMemory-small-allocation", false}, + // We have some unnecessary clears which were needed for GL. + // Ignore message for now, because they're spawned each frame. + {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "ClearCmdBeforeDraw", true}, + {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "vkCmdClearAttachments-clear-after-load", true}, + // TODO: investigate probably false-positive report. + {VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, "vkCmdBeginRenderPass-StoreOpDontCareThenLoadOpLoad", true}, + }; + const auto it = std::find_if(std::begin(hideRules), std::end(hideRules), + [messageType, message = std::string_view{callbackData->pMessage}](const HideRule& hideRule) -> bool + { + return (hideRule.flags & messageType) && message.find(hideRule.pattern) != std::string_view::npos; + }); + if (it == std::end(hideRules)) + LOGWARNING("Vulkan: %s", callbackData->pMessage); + else if (!it->skip) + LOGMESSAGE("Vulkan: %s", callbackData->pMessage); + } + else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + LOGERROR("Vulkan: %s", callbackData->pMessage); + + return VK_FALSE; +} + +// A workaround function to meet calling conventions of Vulkan, SDL and GLAD. +GLADapiproc GetInstanceProcAddr(VkInstance instance, const char* name) +{ +#if SDL_VERSION_ATLEAST(2, 0, 6) + PFN_vkGetInstanceProcAddr function = reinterpret_cast(SDL_Vulkan_GetVkGetInstanceProcAddr()); + return reinterpret_cast(function(instance, name)); +#else + return nullptr; +#endif +} + +} // anonymous namespace + // static -std::unique_ptr CDevice::Create(SDL_Window* UNUSED(window)) +std::unique_ptr CDevice::Create(SDL_Window* window) { + if (!window) + { + LOGERROR("Can't create Vulkan device without window."); + return nullptr; + } + + GLADuserptrloadfunc gladLoadFunction = reinterpret_cast(GetInstanceProcAddr); + std::unique_ptr device(new CDevice()); + device->m_Window = window; + +#ifdef NDEBUG + bool enableDebugMessages = false; + CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages); + bool enableDebugLabels = false; + CFG_GET_VAL("renderer.backend.debuglabels", enableDebugLabels); + bool enableDebugScopedLabels = false; + CFG_GET_VAL("renderer.backend.debugscopedlabels", enableDebugScopedLabels); +#else + bool enableDebugMessages = true; + bool enableDebugLabels = true; + bool enableDebugScopedLabels = true; +#endif + + int gladVulkanVersion = gladLoadVulkanUserPtr(nullptr, gladLoadFunction, nullptr); + if (!gladVulkanVersion) + { + LOGERROR("GLAD unable to load vulkan."); + return nullptr; + } + + VkApplicationInfo applicationInfo{}; + applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + applicationInfo.pApplicationName = "0 A.D."; + applicationInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 27); + applicationInfo.pEngineName = "Pyrogenesis"; + applicationInfo.engineVersion = applicationInfo.applicationVersion; + applicationInfo.apiVersion = VK_API_VERSION_1_1; + + std::vector requiredInstanceExtensions = GetRequiredSDLExtensions(window); + + device->m_ValidationLayers = GetAvailableValidationLayers(); + auto hasValidationLayer = [&layers = device->m_ValidationLayers](const char* name) -> bool + { + return std::find(layers.begin(), layers.end(), name) != layers.end(); + }; + device->m_InstanceExtensions = GetAvailableInstanceExtensions(); + auto hasInstanceExtension = [&extensions = device->m_InstanceExtensions](const char* name) -> bool + { + return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); + }; + +#ifdef NDEBUG + bool enableDebugContext = false; + CFG_GET_VAL("renderer.backend.debugcontext", enableDebugContext); +#else + bool enableDebugContext = true; +#endif + + if (!hasInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) + enableDebugMessages = enableDebugLabels = enableDebugScopedLabels = false; + const bool enableDebugLayers = enableDebugContext || enableDebugMessages || enableDebugLabels || enableDebugScopedLabels; + if (enableDebugLayers) + requiredInstanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + std::vector requestedValidationLayers; + const bool enableValidationFeatures = enableDebugMessages && hasValidationLayer("VK_LAYER_KHRONOS_validation"); + if (enableValidationFeatures) + requestedValidationLayers.emplace_back("VK_LAYER_KHRONOS_validation"); + + // https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/docs/synchronization_usage.md + VkValidationFeatureEnableEXT validationFeatureEnables[] = + { + VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT, + VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT + }; + VkValidationFeaturesEXT validationFeatures{}; + validationFeatures.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT; + validationFeatures.enabledValidationFeatureCount = std::size(validationFeatureEnables); + validationFeatures.pEnabledValidationFeatures = validationFeatureEnables; + + VkInstanceCreateInfo instanceCreateInfo{}; + instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instanceCreateInfo.pApplicationInfo = &applicationInfo; + instanceCreateInfo.enabledExtensionCount = requiredInstanceExtensions.size(); + instanceCreateInfo.ppEnabledExtensionNames = requiredInstanceExtensions.data(); + if (requestedValidationLayers.empty()) + { + instanceCreateInfo.enabledLayerCount = 0; + instanceCreateInfo.ppEnabledLayerNames = nullptr; + } + else + { + instanceCreateInfo.enabledLayerCount = requestedValidationLayers.size(); + instanceCreateInfo.ppEnabledLayerNames = requestedValidationLayers.data(); + } + + // Enabling validation features might significantly reduce performance, + // even more than the standard validation layer. + if (enableValidationFeatures && enableDebugContext) + { + instanceCreateInfo.pNext = &validationFeatures; + } + + const VkResult createInstanceResult = vkCreateInstance(&instanceCreateInfo, nullptr, &device->m_Instance); + if (createInstanceResult != VK_SUCCESS) + { + if (createInstanceResult == VK_ERROR_INCOMPATIBLE_DRIVER) + LOGERROR("Can't create Vulkan instance: incompatible driver."); + else if (createInstanceResult == VK_ERROR_EXTENSION_NOT_PRESENT) + LOGERROR("Can't create Vulkan instance: extension not present."); + else if (createInstanceResult == VK_ERROR_LAYER_NOT_PRESENT) + LOGERROR("Can't create Vulkan instance: layer not present."); + else + LOGERROR("Unknown error during Vulkan instance creation: %d", static_cast(createInstanceResult)); + return nullptr; + } + + gladVulkanVersion = gladLoadVulkanUserPtr(nullptr, gladLoadFunction, device->m_Instance); + if (!gladVulkanVersion) + { + LOGERROR("GLAD unable to re-load vulkan after its instance creation."); + return nullptr; + } + + if (GLAD_VK_EXT_debug_utils && enableDebugMessages) + { + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + debugCreateInfo.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + debugCreateInfo.messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + debugCreateInfo.pfnUserCallback = DebugCallback; + debugCreateInfo.pUserData = nullptr; + + ENSURE_VK_SUCCESS(vkCreateDebugUtilsMessengerEXT( + device->m_Instance, &debugCreateInfo, nullptr, &device->m_DebugMessenger)); + } + + if (window) + ENSURE(SDL_Vulkan_CreateSurface(window, device->m_Instance, &device->m_Surface)); + + const std::vector requiredDeviceExtensions = + { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + std::vector availablePhyscialDevices = + GetAvailablePhysicalDevices(device->m_Instance, device->m_Surface, requiredDeviceExtensions); + for (const SAvailablePhysicalDevice& device : availablePhyscialDevices) + { + LOGMESSAGE("Vulkan available device: '%s' Type: %u Supported: %c", + device.properties.deviceName, static_cast(device.properties.deviceType), + IsPhysicalDeviceUnsupported(device) ? 'N' : 'Y'); + LOGMESSAGE(" ID: %u VendorID: %u API Version: %u Driver Version: %u", + device.properties.deviceID, device.properties.vendorID, + device.properties.apiVersion, device.properties.driverVersion); + for (uint32_t memoryTypeIndex = 0; memoryTypeIndex < device.memoryProperties.memoryTypeCount; ++memoryTypeIndex) + { + const VkMemoryType& type = device.memoryProperties.memoryTypes[memoryTypeIndex]; + LOGMESSAGE(" Memory Type Index: %u Flags: %u Heap Index: %u", + memoryTypeIndex, static_cast(type.propertyFlags), type.heapIndex); + } + for (uint32_t memoryHeapIndex = 0; memoryHeapIndex < device.memoryProperties.memoryHeapCount; ++memoryHeapIndex) + { + const VkMemoryHeap& heap = device.memoryProperties.memoryHeaps[memoryHeapIndex]; + LOGMESSAGE(" Memory Heap Index: %u Size: %zu Flags: %u", + memoryHeapIndex, static_cast(heap.size / 1024), static_cast(heap.flags)); + } + } + device->m_AvailablePhysicalDevices = availablePhyscialDevices; + // We need to remove unsupported devices first. + availablePhyscialDevices.erase( + std::remove_if( + availablePhyscialDevices.begin(), availablePhyscialDevices.end(), + IsPhysicalDeviceUnsupported), + availablePhyscialDevices.end()); + if (availablePhyscialDevices.empty()) + { + LOGERROR("Vulkan can not find any supported and suitable device."); + return nullptr; + } + + int deviceIndexOverride = -1; + CFG_GET_VAL("renderer.backend.vulkan.deviceindexoverride", deviceIndexOverride); + auto choosedDeviceIt = device->m_AvailablePhysicalDevices.end(); + if (deviceIndexOverride >= 0) + { + choosedDeviceIt = std::find_if( + device->m_AvailablePhysicalDevices.begin(), device->m_AvailablePhysicalDevices.end(), + [deviceIndexOverride](const SAvailablePhysicalDevice& availableDevice) + { + return availableDevice.index == static_cast(deviceIndexOverride); + }); + if (choosedDeviceIt == device->m_AvailablePhysicalDevices.end()) + LOGWARNING("Device with override index %d not found.", deviceIndexOverride); + } + if (choosedDeviceIt == device->m_AvailablePhysicalDevices.end()) + { + // We need to choose the best available device fits our needs. + choosedDeviceIt = min_element( + availablePhyscialDevices.begin(), availablePhyscialDevices.end(), + ComparePhysicalDevices); + } + device->m_ChoosenDevice = *choosedDeviceIt; + const SAvailablePhysicalDevice& choosenDevice = device->m_ChoosenDevice; + device->m_AvailablePhysicalDevices.erase(std::remove_if( + device->m_AvailablePhysicalDevices.begin(), device->m_AvailablePhysicalDevices.end(), + [physicalDevice = choosenDevice.device](const SAvailablePhysicalDevice& device) + { + return physicalDevice == device.device; + }), device->m_AvailablePhysicalDevices.end()); + + gladVulkanVersion = gladLoadVulkanUserPtr(choosenDevice.device, gladLoadFunction, device->m_Instance); + if (!gladVulkanVersion) + { + LOGERROR("GLAD unable to re-load vulkan after choosing its physical device."); + return nullptr; + } + + auto hasDeviceExtension = [&extensions = choosenDevice.extensions](const char* name) -> bool + { + return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); + }; + const bool hasDescriptorIndexing = hasDeviceExtension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + const bool hasNeededDescriptorIndexingFeatures = + hasDescriptorIndexing && + choosenDevice.descriptorIndexingProperties.maxUpdateAfterBindDescriptorsInAllPools >= 65536 && + choosenDevice.descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing && + choosenDevice.descriptorIndexingFeatures.runtimeDescriptorArray && + choosenDevice.descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount && + choosenDevice.descriptorIndexingFeatures.descriptorBindingPartiallyBound && + choosenDevice.descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending && + choosenDevice.descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind; + + std::vector deviceExtensions = requiredDeviceExtensions; + if (hasDescriptorIndexing) + deviceExtensions.emplace_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + + device->m_GraphicsQueueFamilyIndex = choosenDevice.graphicsQueueFamilyIndex; + const std::array queueFamilyIndices{{ + choosenDevice.graphicsQueueFamilyIndex + }}; + + PS::StaticVector queueCreateInfos; + const float queuePriority = 1.0f; + std::transform(queueFamilyIndices.begin(), queueFamilyIndices.end(), + std::back_inserter(queueCreateInfos), + [&queuePriority](const size_t queueFamilyIndex) + { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfo.queueCount = 1; + queueCreateInfo.queueFamilyIndex = queueFamilyIndex; + return queueCreateInfo; + }); + + // https://github.com/KhronosGroup/Vulkan-Guide/blob/master/chapters/enabling_features.adoc + VkPhysicalDeviceFeatures deviceFeatures{}; + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + VkPhysicalDeviceDescriptorIndexingFeaturesEXT descriptorIndexingFeatures{}; + + deviceFeatures.textureCompressionBC = choosenDevice.features.textureCompressionBC; + deviceFeatures.samplerAnisotropy = choosenDevice.features.samplerAnisotropy; + + descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = + choosenDevice.descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing; + descriptorIndexingFeatures.runtimeDescriptorArray = + choosenDevice.descriptorIndexingFeatures.runtimeDescriptorArray; + descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = + choosenDevice.descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount; + descriptorIndexingFeatures.descriptorBindingPartiallyBound = + choosenDevice.descriptorIndexingFeatures.descriptorBindingPartiallyBound; + descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending = + choosenDevice.descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending; + descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind = + choosenDevice.descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind; + + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features = deviceFeatures; + if (hasNeededDescriptorIndexingFeatures) + deviceFeatures2.pNext = &descriptorIndexingFeatures; + + VkDeviceCreateInfo deviceCreateInfo{}; + deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceCreateInfo.queueCreateInfoCount = queueCreateInfos.size(); + deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data(); + deviceCreateInfo.enabledExtensionCount = deviceExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); + deviceCreateInfo.pEnabledFeatures = nullptr; + deviceCreateInfo.pNext = &deviceFeatures2; + deviceCreateInfo.enabledLayerCount = 0; + deviceCreateInfo.ppEnabledLayerNames = nullptr; + + const VkResult createDeviceResult = vkCreateDevice( + choosenDevice.device, &deviceCreateInfo, nullptr, &device->m_Device); + if (createDeviceResult != VK_SUCCESS) + { + if (createDeviceResult == VK_ERROR_FEATURE_NOT_PRESENT) + LOGERROR("Can't create Vulkan device: feature not present."); + else if (createDeviceResult == VK_ERROR_EXTENSION_NOT_PRESENT) + LOGERROR("Can't create Vulkan device: extension not present."); + else + LOGERROR("Unknown error during Vulkan device creation: %d", + static_cast(createDeviceResult)); + return nullptr; + } + + VmaVulkanFunctions vulkanFunctions{}; + vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + vulkanFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + vulkanFunctions.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties; + vulkanFunctions.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties; + vulkanFunctions.vkAllocateMemory = vkAllocateMemory; + vulkanFunctions.vkFreeMemory = vkFreeMemory; + vulkanFunctions.vkMapMemory = vkMapMemory; + vulkanFunctions.vkUnmapMemory = vkUnmapMemory; + vulkanFunctions.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges; + vulkanFunctions.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges; + vulkanFunctions.vkBindBufferMemory = vkBindBufferMemory; + vulkanFunctions.vkBindImageMemory = vkBindImageMemory; + vulkanFunctions.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements; + vulkanFunctions.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements; + vulkanFunctions.vkCreateBuffer = vkCreateBuffer; + vulkanFunctions.vkDestroyBuffer = vkDestroyBuffer; + vulkanFunctions.vkCreateImage = vkCreateImage; + vulkanFunctions.vkDestroyImage = vkDestroyImage; + vulkanFunctions.vkCmdCopyBuffer = vkCmdCopyBuffer; + + VmaAllocatorCreateInfo allocatorCreateInfo{}; + allocatorCreateInfo.instance = device->m_Instance; + allocatorCreateInfo.physicalDevice = choosenDevice.device; + allocatorCreateInfo.device = device->m_Device; + allocatorCreateInfo.vulkanApiVersion = applicationInfo.apiVersion; + allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions; + const VkResult createVMAAllocatorResult = + vmaCreateAllocator(&allocatorCreateInfo, &device->m_VMAAllocator); + if (createVMAAllocatorResult != VK_SUCCESS) + { + LOGERROR("Failed to create VMA allocator: %d", + static_cast(createDeviceResult)); + return nullptr; + } + + // We need to use VK_SHARING_MODE_CONCURRENT if we have graphics and present + // in different queues. + vkGetDeviceQueue(device->m_Device, choosenDevice.graphicsQueueFamilyIndex, + 0, &device->m_GraphicsQueue); + ENSURE(device->m_GraphicsQueue != VK_NULL_HANDLE); + + Capabilities& capabilities = device->m_Capabilities; + + capabilities.debugLabels = enableDebugLabels; + capabilities.debugScopedLabels = enableDebugScopedLabels; + capabilities.S3TC = choosenDevice.features.textureCompressionBC; + capabilities.ARBShaders = false; + capabilities.ARBShadersShadow = false; + capabilities.computeShaders = true; + capabilities.instancing = true; + capabilities.maxSampleCount = 1; + const VkSampleCountFlags sampleCountFlags = + choosenDevice.properties.limits.framebufferColorSampleCounts + & choosenDevice.properties.limits.framebufferDepthSampleCounts + & choosenDevice.properties.limits.framebufferStencilSampleCounts; + const std::array allowedSampleCountBits = + { + VK_SAMPLE_COUNT_1_BIT, + VK_SAMPLE_COUNT_2_BIT, + VK_SAMPLE_COUNT_4_BIT, + VK_SAMPLE_COUNT_8_BIT, + VK_SAMPLE_COUNT_16_BIT, + }; + for (size_t index = 0; index < allowedSampleCountBits.size(); ++index) + if (sampleCountFlags & allowedSampleCountBits[index]) + device->m_Capabilities.maxSampleCount = 1u << index; + capabilities.multisampling = device->m_Capabilities.maxSampleCount > 1; + capabilities.anisotropicFiltering = choosenDevice.features.samplerAnisotropy; + capabilities.maxAnisotropy = choosenDevice.properties.limits.maxSamplerAnisotropy; + capabilities.maxTextureSize = + choosenDevice.properties.limits.maxImageDimension2D; + + device->m_RenderPassManager = + std::make_unique(device.get()); + device->m_SamplerManager = std::make_unique(device.get()); + device->m_SubmitScheduler = + std::make_unique( + device.get(), device->m_GraphicsQueueFamilyIndex, device->m_GraphicsQueue); + + bool disableDescriptorIndexing = false; + CFG_GET_VAL("renderer.backend.vulkan.disabledescriptorindexing", disableDescriptorIndexing); + const bool useDescriptorIndexing = hasNeededDescriptorIndexingFeatures && !disableDescriptorIndexing; + device->m_DescriptorManager = + std::make_unique(device.get(), useDescriptorIndexing); + + device->RecreateSwapChain(); + + device->m_Name = choosenDevice.properties.deviceName; + device->m_Version = + std::to_string(VK_API_VERSION_VARIANT(choosenDevice.properties.apiVersion)) + + "." + std::to_string(VK_API_VERSION_MAJOR(choosenDevice.properties.apiVersion)) + + "." + std::to_string(VK_API_VERSION_MINOR(choosenDevice.properties.apiVersion)) + + "." + std::to_string(VK_API_VERSION_PATCH(choosenDevice.properties.apiVersion)); + + device->m_DriverInformation = std::to_string(choosenDevice.properties.driverVersion); + + // Refs: + // * https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html + // * https://pcisig.com/membership/member-companies + device->m_VendorID = std::to_string(choosenDevice.properties.vendorID); + + device->m_Extensions = choosenDevice.extensions; + return device; } CDevice::CDevice() = default; -CDevice::~CDevice() = default; +CDevice::~CDevice() +{ + if (m_Device) + vkDeviceWaitIdle(m_Device); + + // The order of destroying does matter to avoid use-after-free and validation + // layers complaints. + + m_SubmitScheduler.reset(); + + ProcessTextureToDestroyQueue(true); + + m_RenderPassManager.reset(); + m_SamplerManager.reset(); + m_DescriptorManager.reset(); + m_SwapChain.reset(); + + ProcessObjectToDestroyQueue(true); + + if (m_VMAAllocator != VK_NULL_HANDLE) + vmaDestroyAllocator(m_VMAAllocator); + + if (m_Device != VK_NULL_HANDLE) + vkDestroyDevice(m_Device, nullptr); + + if (m_Surface != VK_NULL_HANDLE) + vkDestroySurfaceKHR(m_Instance, m_Surface, nullptr); + + if (GLAD_VK_EXT_debug_utils && m_DebugMessenger) + vkDestroyDebugUtilsMessengerEXT(m_Instance, m_DebugMessenger, nullptr); + + if (m_Instance != VK_NULL_HANDLE) + vkDestroyInstance(m_Instance, nullptr); +} void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { Script::SetProperty(rq, settings, "name", "vulkan"); - std::string vulkanSupport = "unsupported"; - // According to http://wiki.libsdl.org/SDL_Vulkan_LoadLibrary the following - // functionality is supported since SDL 2.0.8. -#if SDL_VERSION_ATLEAST(2, 0, 8) - if (!SDL_Vulkan_LoadLibrary(nullptr)) - { - void* vkGetInstanceProcAddr = SDL_Vulkan_GetVkGetInstanceProcAddr(); - if (vkGetInstanceProcAddr) - vulkanSupport = "supported"; - else - vulkanSupport = "noprocaddr"; - SDL_Vulkan_UnloadLibrary(); - } - else + Script::SetProperty(rq, settings, "extensions", m_Extensions); + + JS::RootedValue device(rq.cx); + Script::CreateObject(rq, &device); + ReportAvailablePhysicalDevice(m_ChoosenDevice, rq, device); + Script::SetProperty(rq, settings, "choosen_device", device); + + JS::RootedValue availableDevices(rq.cx); + Script::CreateArray(rq, &availableDevices, m_AvailablePhysicalDevices.size()); + for (size_t index = 0; index < m_AvailablePhysicalDevices.size(); ++index) { - vulkanSupport = "cantload"; + JS::RootedValue device(rq.cx); + Script::CreateObject(rq, &device); + ReportAvailablePhysicalDevice(m_AvailablePhysicalDevices[index], rq, device); + Script::SetPropertyInt(rq, availableDevices, index, device); } -#endif - Script::SetProperty(rq, settings, "status", vulkanSupport); -} + Script::SetProperty(rq, settings, "available_device", availableDevices); -std::unique_ptr CDevice::CreateCommandContext() -{ - return nullptr; + Script::SetProperty(rq, settings, "instance_extensions", m_InstanceExtensions); + Script::SetProperty(rq, settings, "validation_layers", m_ValidationLayers); } std::unique_ptr CDevice::CreateGraphicsPipelineState( const SGraphicsPipelineStateDesc& pipelineStateDesc) { UNUSED2(pipelineStateDesc); - return nullptr; + return CGraphicsPipelineState::Create(this, pipelineStateDesc); } std::unique_ptr CDevice::CreateVertexInputLayout( const PS::span attributes) { - UNUSED2(attributes); - return nullptr; + return std::make_unique(this, attributes); } std::unique_ptr CDevice::CreateTexture( @@ -98,16 +689,9 @@ const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { - UNUSED2(name); - UNUSED2(type); - UNUSED2(usage); - UNUSED2(format); - UNUSED2(width); - UNUSED2(height); - UNUSED2(defaultSamplerDesc); - UNUSED2(MIPLevelCount); - UNUSED2(sampleCount); - return nullptr; + return CTexture::Create( + this, name, type, usage, format, width, height, + defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateTexture2D( @@ -124,33 +708,46 @@ const char* name, SColorAttachment* colorAttachment, SDepthStencilAttachment* depthStencilAttachment) { - UNUSED2(name); - UNUSED2(colorAttachment); - UNUSED2(depthStencilAttachment); - return nullptr; + return CFramebuffer::Create( + this, name, colorAttachment, depthStencilAttachment); } std::unique_ptr CDevice::CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { - UNUSED2(name); - UNUSED2(type); - UNUSED2(size); - UNUSED2(dynamic); - return nullptr; + return CreateCBuffer(name, type, size, dynamic); +} + +std::unique_ptr CDevice::CreateCBuffer( + const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) +{ + return CBuffer::Create(this, name, type, size, dynamic); } std::unique_ptr CDevice::CreateShaderProgram( const CStr& name, const CShaderDefines& defines) { - UNUSED2(name); - UNUSED2(defines); - return nullptr; + return CShaderProgram::Create(this, name, defines); +} + +std::unique_ptr CDevice::CreateCommandContext() +{ + return CDeviceCommandContext::Create(this); } bool CDevice::AcquireNextBackbuffer() { - return false; + if (!IsSwapChainValid()) + { + vkDeviceWaitIdle(m_Device); + + RecreateSwapChain(); + if (!IsSwapChainValid()) + return false; + } + + PROFILE3("AcquireNextBackbuffer"); + return m_SubmitScheduler->AcquireNextImage(*m_SwapChain); } IFramebuffer* CDevice::GetCurrentBackbuffer( @@ -159,15 +756,24 @@ const AttachmentLoadOp depthStencilAttachmentLoadOp, const AttachmentStoreOp depthStencilAttachmentStoreOp) { - UNUSED2(colorAttachmentLoadOp); - UNUSED2(colorAttachmentStoreOp); - UNUSED2(depthStencilAttachmentLoadOp); - UNUSED2(depthStencilAttachmentStoreOp); - return nullptr; + return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbuffer( + colorAttachmentLoadOp, colorAttachmentStoreOp, + depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp) : nullptr; } void CDevice::Present() { + if (!IsSwapChainValid()) + return; + + PROFILE3("Present"); + + m_SubmitScheduler->Present(*m_SwapChain); + + ProcessObjectToDestroyQueue(); + ProcessTextureToDestroyQueue(); + + ++m_FrameID; } void CDevice::OnWindowResize(const uint32_t width, const uint32_t height) @@ -178,23 +784,173 @@ bool CDevice::IsTextureFormatSupported(const Format format) const { - UNUSED2(format); - return false; + bool supported = false; + switch (format) + { + case Format::UNDEFINED: + break; + + case Format::R8G8B8_UNORM: FALLTHROUGH; + case Format::R8G8B8A8_UNORM: FALLTHROUGH; + case Format::A8_UNORM: FALLTHROUGH; + case Format::L8_UNORM: FALLTHROUGH; + case Format::R32_SFLOAT: FALLTHROUGH; + case Format::R32G32_SFLOAT: FALLTHROUGH; + case Format::R32G32B32_SFLOAT: FALLTHROUGH; + case Format::R32G32B32A32_SFLOAT: FALLTHROUGH; + case Format::D16: FALLTHROUGH; + case Format::D24: FALLTHROUGH; + case Format::D24_S8: FALLTHROUGH; + case Format::D32: + supported = true; + break; + + case Format::BC1_RGB_UNORM: FALLTHROUGH; + case Format::BC1_RGBA_UNORM: FALLTHROUGH; + case Format::BC2_UNORM: FALLTHROUGH; + case Format::BC3_UNORM: + supported = m_Capabilities.S3TC; + break; + + default: + break; + } + return supported; } bool CDevice::IsFramebufferFormatSupported(const Format format) const { - UNUSED2(format); - return false; + VkFormatProperties formatProperties{}; + vkGetPhysicalDeviceFormatProperties( + m_ChoosenDevice.device, Mapping::FromFormat(format), &formatProperties); + if (IsDepthFormat(format)) + return formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; + return formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; } Format CDevice::GetPreferredDepthStencilFormat( - const uint32_t usage, const bool depth, const bool stencil) const + const uint32_t UNUSED(usage), const bool depth, const bool stencil) const +{ + // TODO: account usage. + ENSURE(depth || stencil); + Format format = Format::UNDEFINED; + if (stencil) + { + format = Format::D24_S8; + } + else + { + // TODO: add most known vendors to enum. + // https://developer.nvidia.com/blog/vulkan-dos-donts/ + if (m_ChoosenDevice.properties.vendorID == 0x10DE) + format = Format::D24; + else + format = Format::D24; + } + ENSURE(IsFramebufferFormatSupported(format)); + return format; +} + +void CDevice::ScheduleObjectToDestroy( + VkObjectType type, const uint64_t handle, const VmaAllocation allocation) { - UNUSED2(usage); - UNUSED2(depth); - UNUSED2(stencil); - return Format::UNDEFINED; + m_ObjectToDestroyQueue.push({m_FrameID, type, handle, allocation}); +} + +void CDevice::ScheduleTextureToDestroy(const CTexture::UID uid) +{ + m_TextureToDestroyQueue.push({m_FrameID, uid}); +} + +void CDevice::SetObjectName(VkObjectType type, const uint64_t handle, const char* name) +{ + if (!m_Capabilities.debugLabels) + return; + VkDebugUtilsObjectNameInfoEXT nameInfo{}; + nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + nameInfo.objectType = type; + nameInfo.objectHandle = handle; + nameInfo.pObjectName = name; + vkSetDebugUtilsObjectNameEXT(m_Device, &nameInfo); +} + +std::unique_ptr CDevice::CreateRingCommandContext(const size_t size) +{ + return std::make_unique( + this, size, m_GraphicsQueueFamilyIndex, *m_SubmitScheduler); +} + +void CDevice::RecreateSwapChain() +{ + int surfaceDrawableWidth = 0, surfaceDrawableHeight = 0; + SDL_Vulkan_GetDrawableSize(m_Window, &surfaceDrawableWidth, &surfaceDrawableHeight); + m_SwapChain = CSwapChain::Create( + this, m_Surface, surfaceDrawableWidth, surfaceDrawableHeight, std::move(m_SwapChain)); +} + +bool CDevice::IsSwapChainValid() +{ + return m_SwapChain && m_SwapChain->IsValid(); +} + +void CDevice::ProcessObjectToDestroyQueue(const bool ignoreFrameID) +{ + while (!m_ObjectToDestroyQueue.empty() && + (ignoreFrameID || m_ObjectToDestroyQueue.front().frameID + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID)) + { + ObjectToDestroy& object = m_ObjectToDestroyQueue.front(); +#if VK_USE_64_BIT_PTR_DEFINES + void* handle = reinterpret_cast(object.handle); +#else + const uint64_t handle = object.handle; +#endif + switch (object.type) + { + case VK_OBJECT_TYPE_IMAGE: + vmaDestroyImage(GetVMAAllocator(), static_cast(handle), object.allocation); + break; + case VK_OBJECT_TYPE_BUFFER: + vmaDestroyBuffer(GetVMAAllocator(), static_cast(handle), object.allocation); + break; + case VK_OBJECT_TYPE_IMAGE_VIEW: + vkDestroyImageView(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_BUFFER_VIEW: + vkDestroyBufferView(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_FRAMEBUFFER: + vkDestroyFramebuffer(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_RENDER_PASS: + vkDestroyRenderPass(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_SAMPLER: + vkDestroySampler(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_SHADER_MODULE: + vkDestroyShaderModule(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_PIPELINE_LAYOUT: + vkDestroyPipelineLayout(m_Device, static_cast(handle), nullptr); + break; + case VK_OBJECT_TYPE_PIPELINE: + vkDestroyPipeline(m_Device, static_cast(handle), nullptr); + break; + default: + debug_warn("Unsupported object to destroy type."); + } + m_ObjectToDestroyQueue.pop(); + } +} + +void CDevice::ProcessTextureToDestroyQueue(const bool ignoreFrameID) +{ + while (!m_TextureToDestroyQueue.empty() && + (ignoreFrameID || m_TextureToDestroyQueue.front().first + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID)) + { + GetDescriptorManager().OnTextureDestroy(m_TextureToDestroyQueue.front().second); + m_TextureToDestroyQueue.pop(); + } } std::unique_ptr CreateDevice(SDL_Window* window) Index: ps/trunk/source/renderer/backend/vulkan/DeviceCommandContext.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DeviceCommandContext.h +++ ps/trunk/source/renderer/backend/vulkan/DeviceCommandContext.h @@ -0,0 +1,196 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT +#define INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT + +#include "renderer/backend/IBuffer.h" +#include "renderer/backend/IDeviceCommandContext.h" + +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CBuffer; +class CDevice; +class CFramebuffer; +class CGraphicsPipelineState; +class CRingCommandContext; +class CShaderProgram; +class CVertexInputLayout; + +class CDeviceCommandContext final : public IDeviceCommandContext +{ +public: + ~CDeviceCommandContext() override; + + IDevice* GetDevice() override; + + void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override; + + void BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override; + + void ClearFramebuffer(const bool color, const bool depth, const bool stencil) override; + void BeginFramebufferPass(IFramebuffer* framebuffer) override; + void EndFramebufferPass() override; + void ReadbackFramebufferSync( + const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, + void* data) override; + + void UploadTexture(ITexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t level = 0, const uint32_t layer = 0) override; + void UploadTextureRegion(ITexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t xOffset, const uint32_t yOffset, + const uint32_t width, const uint32_t height, + const uint32_t level = 0, const uint32_t layer = 0) override; + + void UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) override; + void UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction) override; + void UploadBufferRegion( + IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) override; + void UploadBufferRegion( + IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, + const UploadBufferFunction& uploadFunction) override; + + void SetScissors(const uint32_t scissorCount, const Rect* scissors) override; + void SetViewports(const uint32_t viewportCount, const Rect* viewports) override; + + void SetVertexInputLayout( + IVertexInputLayout* vertexInputLayout) override; + + void SetVertexBuffer( + const uint32_t bindingSlot, IBuffer* buffer, const uint32_t offset) override; + void SetVertexBufferData( + const uint32_t bindingSlot, const void* data, const uint32_t dataSize) override; + + void SetIndexBuffer(IBuffer* buffer) override; + void SetIndexBufferData(const void* data, const uint32_t dataSize) override; + + void BeginPass() override; + void EndPass() override; + + void Draw(const uint32_t firstVertex, const uint32_t vertexCount) override; + void DrawIndexed( + const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) override; + void DrawInstanced( + const uint32_t firstVertex, const uint32_t vertexCount, + const uint32_t firstInstance, const uint32_t instanceCount) override; + void DrawIndexedInstanced( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t firstInstance, const uint32_t instanceCount, + const int32_t vertexOffset) override; + void DrawIndexedInRange( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t start, const uint32_t end) override; + + void SetTexture(const int32_t bindingSlot, ITexture* texture) override; + + void SetUniform( + const int32_t bindingSlot, + const float value) override; + void SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY) override; + void SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ) override; + void SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ, const float valueW) override; + void SetUniform( + const int32_t bindingSlot, PS::span values) override; + + void BeginScopedLabel(const char* name) override; + void EndScopedLabel() override; + + void Flush() override; + +private: + friend class CDevice; + + static std::unique_ptr Create(CDevice* device); + + CDeviceCommandContext(); + + void PreDraw(); + void ApplyPipelineStateIfDirty(); + void BindVertexBuffer(const uint32_t bindingSlot, CBuffer* buffer, uint32_t offset); + void BindIndexBuffer(CBuffer* buffer, uint32_t offset); + + CDevice* m_Device = nullptr; + + bool m_DebugScopedLabels = false; + + std::unique_ptr m_PrependCommandContext; + std::unique_ptr m_CommandContext; + + CGraphicsPipelineState* m_GraphicsPipelineState = nullptr; + CVertexInputLayout* m_VertexInputLayout = nullptr; + CFramebuffer* m_Framebuffer = nullptr; + CShaderProgram* m_ShaderProgram = nullptr; + bool m_IsPipelineStateDirty = true; + VkPipeline m_LastBoundPipeline = VK_NULL_HANDLE; + + bool m_InsideFramebufferPass = false; + bool m_InsidePass = false; + + // Currently bound buffers to skip the same buffer bind. + CBuffer* m_BoundIndexBuffer = nullptr; + uint32_t m_BoundIndexBufferOffset = 0; + + // TODO: reduce code duplication. + std::unique_ptr m_UniformBuffer; + std::unique_ptr m_UniformStagingBuffer; + VkDescriptorPool m_UniformDescriptorPool = VK_NULL_HANDLE; + VkDescriptorSet m_UniformDescriptorSet = VK_NULL_HANDLE; + // TODO: combine buffers. + // Vertex buffer for in-place vertex data. + std::unique_ptr m_InPlaceVertexBuffer; + std::unique_ptr m_InPlaceIndexBuffer; + std::unique_ptr m_InPlaceVertexStagingBuffer; + std::unique_ptr m_InPlaceIndexStagingBuffer; + void* m_InPlaceVertexStagingBufferMappedData = nullptr; + void* m_InPlaceIndexStagingBufferMappedData = nullptr; + void* m_UniformStagingBufferMappedData = nullptr; + // TODO: add descriptions. + uint32_t m_InPlaceBlockIndex = 0; + uint32_t m_InPlaceBlockVertexOffset = 0; + uint32_t m_InPlaceBlockIndexOffset = 0; + uint32_t m_UniformOffset = 0; + uint32_t m_UniformIndexOffset = 0; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT Index: ps/trunk/source/renderer/backend/vulkan/DeviceCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DeviceCommandContext.cpp +++ ps/trunk/source/renderer/backend/vulkan/DeviceCommandContext.cpp @@ -0,0 +1,962 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "DeviceCommandContext.h" + +#include "maths/MathUtil.h" +#include "ps/CLogger.h" +#include "ps/containers/Span.h" +#include "ps/containers/StaticVector.h" +#include "renderer/backend/vulkan/Buffer.h" +#include "renderer/backend/vulkan/DescriptorManager.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Framebuffer.h" +#include "renderer/backend/vulkan/PipelineState.h" +#include "renderer/backend/vulkan/RingCommandContext.h" +#include "renderer/backend/vulkan/ShaderProgram.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/Utilities.h" + +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace +{ + +constexpr uint32_t UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024; +constexpr uint32_t FRAME_INPLACE_BUFFER_SIZE = 1024 * 1024; + +struct SBaseImageState +{ + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkAccessFlags accessMask = 0; + VkPipelineStageFlags stageMask = 0; +}; + +SBaseImageState GetBaseImageState(CTexture* texture) +{ + if (texture->GetUsage() & ITexture::Usage::SAMPLED) + { + return { + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT}; + } + else if (texture->GetUsage() & ITexture::Usage::COLOR_ATTACHMENT) + { + return { + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + } + else if (texture->GetUsage() & ITexture::Usage::DEPTH_STENCIL_ATTACHMENT) + { + return { + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT}; + } + return {}; +} + +class ScopedImageLayoutTransition +{ +public: + ScopedImageLayoutTransition( + CRingCommandContext& commandContext, const PS::span textures, + const VkImageLayout layout, const VkAccessFlags accessMask, const VkPipelineStageFlags stageMask) + : m_CommandContext(commandContext), m_Textures(textures), m_Layout(layout), + m_AccessMask(accessMask), m_StageMask(stageMask) + { + for (CTexture* const texture : m_Textures) + { + const auto state = GetBaseImageState(texture); + + VkImageLayout oldLayout = state.layout; + if (!texture->IsInitialized()) + oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + Utilities::SetTextureLayout( + m_CommandContext.GetCommandBuffer(), texture, + oldLayout, m_Layout, + state.accessMask, m_AccessMask, state.stageMask, m_StageMask); + } + } + + ~ScopedImageLayoutTransition() + { + for (CTexture* const texture : m_Textures) + { + const auto state = GetBaseImageState(texture); + + Utilities::SetTextureLayout( + m_CommandContext.GetCommandBuffer(), texture, + m_Layout, state.layout, + m_AccessMask, state.accessMask, m_StageMask, state.stageMask); + } + } + +private: + CRingCommandContext& m_CommandContext; + const PS::span m_Textures; + const VkImageLayout m_Layout = VK_IMAGE_LAYOUT_UNDEFINED; + const VkAccessFlags m_AccessMask = 0; + const VkPipelineStageFlags m_StageMask = 0; +}; + +} // anonymous namespace + +// static +std::unique_ptr CDeviceCommandContext::Create(CDevice* device) +{ + std::unique_ptr deviceCommandContext(new CDeviceCommandContext()); + deviceCommandContext->m_Device = device; + deviceCommandContext->m_DebugScopedLabels = device->GetCapabilities().debugScopedLabels; + deviceCommandContext->m_PrependCommandContext = + device->CreateRingCommandContext(NUMBER_OF_FRAMES_IN_FLIGHT); + deviceCommandContext->m_CommandContext = + device->CreateRingCommandContext(NUMBER_OF_FRAMES_IN_FLIGHT); + + deviceCommandContext->m_InPlaceVertexBuffer = device->CreateCBuffer( + "InPlaceVertexBuffer", IBuffer::Type::VERTEX, FRAME_INPLACE_BUFFER_SIZE, true); + deviceCommandContext->m_InPlaceIndexBuffer = device->CreateCBuffer( + "InPlaceIndexBuffer", IBuffer::Type::INDEX, FRAME_INPLACE_BUFFER_SIZE, true); + + deviceCommandContext->m_InPlaceVertexStagingBuffer = device->CreateCBuffer( + "InPlaceVertexStagingBuffer", IBuffer::Type::UPLOAD, NUMBER_OF_FRAMES_IN_FLIGHT * FRAME_INPLACE_BUFFER_SIZE, true); + deviceCommandContext->m_InPlaceIndexStagingBuffer = device->CreateCBuffer( + "InPlaceIndexStagingBuffer", IBuffer::Type::UPLOAD, NUMBER_OF_FRAMES_IN_FLIGHT * FRAME_INPLACE_BUFFER_SIZE, true); + + deviceCommandContext->m_UniformBuffer = device->CreateCBuffer( + "UniformBuffer", IBuffer::Type::UNIFORM, UNIFORM_BUFFER_SIZE, true); + deviceCommandContext->m_UniformStagingBuffer = device->CreateCBuffer( + "UniformStagingBuffer", IBuffer::Type::UPLOAD, NUMBER_OF_FRAMES_IN_FLIGHT * UNIFORM_BUFFER_SIZE, true); + + deviceCommandContext->m_InPlaceVertexStagingBufferMappedData = + deviceCommandContext->m_InPlaceVertexStagingBuffer->GetMappedData(); + ENSURE(deviceCommandContext->m_InPlaceVertexStagingBufferMappedData); + deviceCommandContext->m_InPlaceIndexStagingBufferMappedData = + deviceCommandContext->m_InPlaceIndexStagingBuffer->GetMappedData(); + ENSURE(deviceCommandContext->m_InPlaceIndexStagingBufferMappedData); + deviceCommandContext->m_UniformStagingBufferMappedData = + deviceCommandContext->m_UniformStagingBuffer->GetMappedData(); + ENSURE(deviceCommandContext->m_UniformStagingBufferMappedData); + + // TODO: reduce the code duplication. + VkDescriptorPoolSize descriptorPoolSize{}; + descriptorPoolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + descriptorPoolSize.descriptorCount = 1; + + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{}; + descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCreateInfo.poolSizeCount = 1; + descriptorPoolCreateInfo.pPoolSizes = &descriptorPoolSize; + descriptorPoolCreateInfo.maxSets = 1; + ENSURE_VK_SUCCESS(vkCreateDescriptorPool( + device->GetVkDevice(), &descriptorPoolCreateInfo, nullptr, &deviceCommandContext->m_UniformDescriptorPool)); + + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{}; + descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocateInfo.descriptorPool = deviceCommandContext->m_UniformDescriptorPool; + descriptorSetAllocateInfo.descriptorSetCount = 1; + descriptorSetAllocateInfo.pSetLayouts = &device->GetDescriptorManager().GetUniformDescriptorSetLayout(); + + ENSURE_VK_SUCCESS(vkAllocateDescriptorSets( + device->GetVkDevice(), &descriptorSetAllocateInfo, &deviceCommandContext->m_UniformDescriptorSet)); + + // TODO: fix the hard-coded size. + const VkDescriptorBufferInfo descriptorBufferInfos[1] = + { + {deviceCommandContext->m_UniformBuffer->GetVkBuffer(), 0u, 512u} + }; + + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.dstSet = deviceCommandContext->m_UniformDescriptorSet; + writeDescriptorSet.dstBinding = 0; + writeDescriptorSet.dstArrayElement = 0; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.pBufferInfo = descriptorBufferInfos; + + vkUpdateDescriptorSets( + device->GetVkDevice(), 1, &writeDescriptorSet, 0, nullptr); + + return deviceCommandContext; +} + +CDeviceCommandContext::CDeviceCommandContext() = default; + +CDeviceCommandContext::~CDeviceCommandContext() +{ + VkDevice device = m_Device->GetVkDevice(); + + vkDeviceWaitIdle(device); + + if (m_UniformDescriptorPool != VK_NULL_HANDLE) + vkDestroyDescriptorPool(device, m_UniformDescriptorPool, nullptr); +} + +IDevice* CDeviceCommandContext::GetDevice() +{ + return m_Device; +} + +void CDeviceCommandContext::SetGraphicsPipelineState( + IGraphicsPipelineState* pipelineState) +{ + ENSURE(pipelineState); + m_GraphicsPipelineState = pipelineState->As(); + + CShaderProgram* shaderProgram = m_GraphicsPipelineState->GetShaderProgram()->As(); + if (m_ShaderProgram != shaderProgram) + { + if (m_ShaderProgram) + m_ShaderProgram->Unbind(); + m_ShaderProgram = shaderProgram; + } + m_IsPipelineStateDirty = true; +} + +void CDeviceCommandContext::BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) +{ + ENSURE(!m_InsideFramebufferPass); + const auto& sourceColorAttachments = + sourceFramebuffer->As()->GetColorAttachments(); + const auto& destinationColorAttachments = + destinationFramebuffer->As()->GetColorAttachments(); + ENSURE(sourceColorAttachments.size() == destinationColorAttachments.size()); + // TODO: account depth. + //ENSURE( + // static_cast(sourceFramebuffer->As()->GetDepthStencilAttachment()) == + // static_cast(destinationFramebuffer->As()->GetDepthStencilAttachment())); + + for (CTexture* sourceColorAttachment : sourceColorAttachments) + { + ENSURE(sourceColorAttachment->GetUsage() & ITexture::Usage::TRANSFER_SRC); + } + for (CTexture* destinationColorAttachment : destinationColorAttachments) + { + ENSURE(destinationColorAttachment->GetUsage() & ITexture::Usage::TRANSFER_DST); + } + + // TODO: combine barriers, reduce duplication, add depth. + ScopedImageLayoutTransition scopedColorAttachmentsTransition{ + *m_CommandContext, + {sourceColorAttachments.begin(), sourceColorAttachments.end()}, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT}; + ScopedImageLayoutTransition destinationColorAttachmentsTransition{ + *m_CommandContext, + {destinationColorAttachments.begin(), destinationColorAttachments.end()}, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT}; + + // TODO: split BlitFramebuffer into ResolveFramebuffer and BlitFramebuffer. + if (sourceFramebuffer->As()->GetSampleCount() == 1) + { + // TODO: we need to check for VK_FORMAT_FEATURE_BLIT_*_BIT for used formats. + for (size_t index = 0; index < destinationColorAttachments.size(); ++index) + { + CTexture* sourceColorAttachment = sourceColorAttachments[index]; + CTexture* destinationColorAttachment = destinationColorAttachments[index]; + + VkImageBlit region{}; + region.srcOffsets[1].x = sourceColorAttachment->GetWidth(); + region.srcOffsets[1].y = sourceColorAttachment->GetHeight(); + region.srcOffsets[1].z = 1; + region.dstOffsets[1].x = destinationColorAttachment->GetWidth(); + region.dstOffsets[1].y = destinationColorAttachment->GetHeight(); + region.dstOffsets[1].z = 1; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.dstSubresource.mipLevel = 0; + region.dstSubresource.baseArrayLayer = 0; + region.dstSubresource.layerCount = 1; + + ENSURE(sourceColorAttachment->GetImage() != VK_NULL_HANDLE); + ENSURE(destinationColorAttachment->GetImage() != VK_NULL_HANDLE); + vkCmdBlitImage( + m_CommandContext->GetCommandBuffer(), + sourceColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + destinationColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion, VK_FILTER_NEAREST); + } + } + else + { + ENSURE(sourceFramebuffer->As()->GetSampleCount() > 1); + ENSURE(destinationFramebuffer->As()->GetSampleCount() == 1); + ENSURE(sourceFramebuffer->As()->GetWidth() == destinationFramebuffer->As()->GetWidth()); + ENSURE(sourceFramebuffer->As()->GetHeight() == destinationFramebuffer->As()->GetHeight()); + for (size_t index = 0; index < destinationColorAttachments.size(); ++index) + { + CTexture* sourceColorAttachment = sourceColorAttachments[index]; + CTexture* destinationColorAttachment = destinationColorAttachments[index]; + + VkImageResolve region{}; + region.extent.width = sourceColorAttachment->GetWidth(); + region.extent.height = sourceColorAttachment->GetHeight(); + region.extent.depth = 1; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.dstSubresource.mipLevel = 0; + region.dstSubresource.baseArrayLayer = 0; + region.dstSubresource.layerCount = 1; + + vkCmdResolveImage( + m_CommandContext->GetCommandBuffer(), + sourceColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + destinationColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion); + } + } +} + +void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil) +{ + ENSURE(m_InsideFramebufferPass); + ENSURE(m_Framebuffer); + PS::StaticVector clearAttachments; + if (color) + { + ENSURE(!m_Framebuffer->GetColorAttachments().empty()); + for (size_t index = 0; index < m_Framebuffer->GetColorAttachments().size(); ++index) + { + VkClearAttachment clearAttachment{}; + clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + const CColor& clearColor = m_Framebuffer->GetClearColor(); + clearAttachment.clearValue.color.float32[0] = clearColor.r; + clearAttachment.clearValue.color.float32[1] = clearColor.g; + clearAttachment.clearValue.color.float32[2] = clearColor.b; + clearAttachment.clearValue.color.float32[3] = clearColor.a; + clearAttachment.colorAttachment = index; + clearAttachments.emplace_back(std::move(clearAttachment)); + } + } + if (depth || stencil) + { + ENSURE(m_Framebuffer->GetDepthStencilAttachment()); + if (stencil) + ENSURE(m_Framebuffer->GetDepthStencilAttachment()->GetFormat() == Format::D24_S8); + VkClearAttachment clearAttachment{}; + if (depth) + clearAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + if (stencil) + clearAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + clearAttachment.clearValue.depthStencil.depth = 1.0f; + clearAttachment.clearValue.depthStencil.stencil = 0; + clearAttachments.emplace_back(std::move(clearAttachment)); + } + VkClearRect clearRect{}; + clearRect.layerCount = 1; + clearRect.rect.offset.x = 0; + clearRect.rect.offset.y = 0; + clearRect.rect.extent.width = m_Framebuffer->GetWidth(); + clearRect.rect.extent.height = m_Framebuffer->GetHeight(); + vkCmdClearAttachments( + m_CommandContext->GetCommandBuffer(), + clearAttachments.size(), clearAttachments.data(), + 1, &clearRect); +} + +void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer) +{ + ENSURE(framebuffer); + m_IsPipelineStateDirty = true; + m_Framebuffer = framebuffer->As(); + m_GraphicsPipelineState = nullptr; + m_VertexInputLayout = nullptr; + + SetScissors(0, nullptr); + + for (CTexture* colorAttachment : m_Framebuffer->GetColorAttachments()) + { + if (!(colorAttachment->GetUsage() & ITexture::Usage::SAMPLED) && colorAttachment->IsInitialized()) + continue; + VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + if (!colorAttachment->IsInitialized()) + oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + Utilities::SetTextureLayout( + m_CommandContext->GetCommandBuffer(), colorAttachment, + oldLayout, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + } + + CTexture* depthStencilAttachment = m_Framebuffer->GetDepthStencilAttachment(); + if (depthStencilAttachment && ((depthStencilAttachment->GetUsage() & ITexture::Usage::SAMPLED) || !depthStencilAttachment->IsInitialized())) + { + VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + if (!depthStencilAttachment->IsInitialized()) + oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + Utilities::SetTextureLayout( + m_CommandContext->GetCommandBuffer(), depthStencilAttachment, oldLayout, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ACCESS_SHADER_READ_BIT, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT); + } + + m_InsideFramebufferPass = true; + + VkRenderPassBeginInfo renderPassBeginInfo{}; + renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBeginInfo.renderPass = m_Framebuffer->GetRenderPass(); + renderPassBeginInfo.framebuffer = m_Framebuffer->GetFramebuffer(); + renderPassBeginInfo.renderArea.offset = { 0, 0 }; + renderPassBeginInfo.renderArea.extent = { m_Framebuffer->GetWidth(), m_Framebuffer->GetHeight() }; + + PS::StaticVector clearValues; + const bool needsClearValues = + m_Framebuffer->GetColorAttachmentLoadOp() == AttachmentLoadOp::CLEAR || + (m_Framebuffer->GetDepthStencilAttachment() && + m_Framebuffer->GetDepthStencilAttachmentLoadOp() == AttachmentLoadOp::CLEAR); + if (needsClearValues) + { + for (CTexture* colorAttachment : m_Framebuffer->GetColorAttachments()) + { + UNUSED2(colorAttachment); + const CColor& clearColor = m_Framebuffer->GetClearColor(); + // The four array elements of the clear color map to R, G, B, and A + // components of image formats, in order. + clearValues.emplace_back(); + clearValues.back().color.float32[0] = clearColor.r; + clearValues.back().color.float32[1] = clearColor.g; + clearValues.back().color.float32[2] = clearColor.b; + clearValues.back().color.float32[3] = clearColor.a; + } + if (m_Framebuffer->GetDepthStencilAttachment()) + { + clearValues.emplace_back(); + clearValues.back().depthStencil.depth = 1.0f; + clearValues.back().depthStencil.stencil = 0; + } + renderPassBeginInfo.clearValueCount = clearValues.size(); + renderPassBeginInfo.pClearValues = clearValues.data(); + } + + vkCmdBeginRenderPass(m_CommandContext->GetCommandBuffer(), &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); +} + +void CDeviceCommandContext::EndFramebufferPass() +{ + ENSURE(m_InsideFramebufferPass); + vkCmdEndRenderPass(m_CommandContext->GetCommandBuffer()); + + m_InsideFramebufferPass = false; + m_BoundIndexBuffer = nullptr; + + ENSURE(m_Framebuffer); + for (CTexture* colorAttachment : m_Framebuffer->GetColorAttachments()) + { + if (!(colorAttachment->GetUsage() & ITexture::Usage::SAMPLED)) + continue; + Utilities::SetTextureLayout( + m_CommandContext->GetCommandBuffer(), colorAttachment, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + } + + CTexture* depthStencilAttachment = m_Framebuffer->GetDepthStencilAttachment(); + if (depthStencilAttachment && (depthStencilAttachment->GetUsage() & ITexture::Usage::SAMPLED)) + { + Utilities::SetTextureLayout( + m_CommandContext->GetCommandBuffer(), depthStencilAttachment, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + } + + m_LastBoundPipeline = VK_NULL_HANDLE; + if (m_ShaderProgram) + m_ShaderProgram->Unbind(); + m_ShaderProgram = nullptr; +} + +void CDeviceCommandContext::ReadbackFramebufferSync( + const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height, + void* data) +{ + UNUSED2(x); + UNUSED2(y); + UNUSED2(width); + UNUSED2(height); + UNUSED2(data); + LOGERROR("Vulkan: framebuffer readback is not implemented yet."); +} + +void CDeviceCommandContext::UploadTexture(ITexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t level, const uint32_t layer) +{ + (m_InsideFramebufferPass ? m_PrependCommandContext : m_CommandContext)->ScheduleUpload( + texture->As(), dataFormat, data, dataSize, level, layer); +} + +void CDeviceCommandContext::UploadTextureRegion(ITexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t xOffset, const uint32_t yOffset, + const uint32_t width, const uint32_t height, + const uint32_t level, const uint32_t layer) +{ + (m_InsideFramebufferPass ? m_PrependCommandContext : m_CommandContext)->ScheduleUpload( + texture->As(), dataFormat, data, dataSize, xOffset, yOffset, width, height, level, layer); +} + +void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize) +{ + ENSURE(!m_InsideFramebufferPass); + m_CommandContext->ScheduleUpload( + buffer->As(), data, 0, dataSize); +} + +void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction) +{ + ENSURE(!m_InsideFramebufferPass); + m_CommandContext->ScheduleUpload( + buffer->As(), 0, buffer->As()->GetSize(), uploadFunction); +} + +void CDeviceCommandContext::UploadBufferRegion( + IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) +{ + ENSURE(!m_InsideFramebufferPass); + m_CommandContext->ScheduleUpload( + buffer->As(), data, dataOffset, dataSize); +} + +void CDeviceCommandContext::UploadBufferRegion( + IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, + const UploadBufferFunction& uploadFunction) +{ + m_CommandContext->ScheduleUpload( + buffer->As(), dataOffset, dataSize, uploadFunction); +} + +void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors) +{ + ENSURE(m_Framebuffer); + ENSURE(scissorCount <= 1); + VkRect2D scissor{}; + if (scissorCount == 1) + { + // the x and y members of offset member of any element of pScissors must be + // greater than or equal to 0. + int32_t x = scissors[0].x; + int32_t y = m_Framebuffer->GetHeight() - scissors[0].y - scissors[0].height; + int32_t width = scissors[0].width; + int32_t height = scissors[0].height; + if (x < 0) + { + width = std::max(0, width + x); + x = 0; + } + if (y < 0) + { + height = std::max(0, height + y); + y = 0; + } + scissor.offset.x = x; + scissor.offset.y = y; + scissor.extent.width = width; + scissor.extent.height = height; + } + else + { + scissor.extent.width = m_Framebuffer->GetWidth(); + scissor.extent.height = m_Framebuffer->GetHeight(); + } + vkCmdSetScissor(m_CommandContext->GetCommandBuffer(), 0, 1, &scissor); +} + +void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports) +{ + ENSURE(m_Framebuffer); + ENSURE(viewportCount == 1); + + VkViewport viewport{}; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + viewport.x = static_cast(viewports[0].x); + viewport.y = static_cast(static_cast(m_Framebuffer->GetHeight()) - viewports[0].y - viewports[0].height); + viewport.width = static_cast(viewports[0].width); + viewport.height = static_cast(viewports[0].height); + + vkCmdSetViewport(m_CommandContext->GetCommandBuffer(), 0, 1, &viewport); +} + +void CDeviceCommandContext::SetVertexInputLayout( + IVertexInputLayout* vertexInputLayout) +{ + ENSURE(vertexInputLayout); + m_IsPipelineStateDirty = true; + m_VertexInputLayout = vertexInputLayout->As(); +} + +void CDeviceCommandContext::SetVertexBuffer( + const uint32_t bindingSlot, IBuffer* buffer, const uint32_t offset) +{ + BindVertexBuffer(bindingSlot, buffer->As(), offset); +} + +void CDeviceCommandContext::SetVertexBufferData( + const uint32_t bindingSlot, const void* data, const uint32_t dataSize) +{ + // TODO: check vertex buffer alignment. + const uint32_t ALIGNMENT = 32; + + uint32_t destination = m_InPlaceBlockIndex * FRAME_INPLACE_BUFFER_SIZE + m_InPlaceBlockVertexOffset; + uint32_t destination2 = m_InPlaceBlockVertexOffset; + // TODO: add overflow checks. + m_InPlaceBlockVertexOffset = (m_InPlaceBlockVertexOffset + dataSize + ALIGNMENT - 1) & ~(ALIGNMENT - 1); + std::memcpy(static_cast(m_InPlaceVertexStagingBufferMappedData) + destination, data, dataSize); + + BindVertexBuffer(bindingSlot, m_InPlaceVertexBuffer.get(), destination2); +} + +void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer) +{ + BindIndexBuffer(buffer->As(), 0); +} + +void CDeviceCommandContext::SetIndexBufferData( + const void* data, const uint32_t dataSize) +{ + // TODO: check index buffer alignment. + const uint32_t ALIGNMENT = 32; + + uint32_t destination = m_InPlaceBlockIndex * FRAME_INPLACE_BUFFER_SIZE + m_InPlaceBlockIndexOffset; + uint32_t destination2 = m_InPlaceBlockIndexOffset; + // TODO: add overflow checks. + m_InPlaceBlockIndexOffset = (m_InPlaceBlockIndexOffset + dataSize + ALIGNMENT - 1) & (~(ALIGNMENT - 1)); + std::memcpy(static_cast(m_InPlaceIndexStagingBufferMappedData) + destination, data, dataSize); + + BindIndexBuffer(m_InPlaceIndexBuffer.get(), destination2); +} + +void CDeviceCommandContext::BeginPass() +{ + ENSURE(m_InsideFramebufferPass); + m_InsidePass = true; +} + +void CDeviceCommandContext::EndPass() +{ + ENSURE(m_InsidePass); + m_InsidePass = false; +} + +void CDeviceCommandContext::Draw(const uint32_t firstVertex, const uint32_t vertexCount) +{ + PreDraw(); + vkCmdDraw(m_CommandContext->GetCommandBuffer(), vertexCount, 1, firstVertex, 0); +} + +void CDeviceCommandContext::DrawIndexed( + const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) +{ + ENSURE(vertexOffset == 0); + PreDraw(); + vkCmdDrawIndexed(m_CommandContext->GetCommandBuffer(), indexCount, 1, firstIndex, 0, 0); +} + +void CDeviceCommandContext::DrawInstanced( + const uint32_t firstVertex, const uint32_t vertexCount, + const uint32_t firstInstance, const uint32_t instanceCount) +{ + PreDraw(); + vkCmdDraw( + m_CommandContext->GetCommandBuffer(), vertexCount, instanceCount, firstVertex, firstInstance); +} + +void CDeviceCommandContext::DrawIndexedInstanced( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t firstInstance, const uint32_t instanceCount, + const int32_t vertexOffset) +{ + PreDraw(); + vkCmdDrawIndexed( + m_CommandContext->GetCommandBuffer(), indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); +} + +void CDeviceCommandContext::DrawIndexedInRange( + const uint32_t firstIndex, const uint32_t indexCount, + const uint32_t UNUSED(start), const uint32_t UNUSED(end)) +{ + PreDraw(); + DrawIndexed(firstIndex, indexCount, 0); +} + +void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture) +{ + if (bindingSlot < 0) + return; + + ENSURE(m_InsidePass); + ENSURE(texture); + CTexture* textureToBind = texture->As(); + ENSURE(textureToBind->GetUsage() & ITexture::Usage::SAMPLED); + + if (!m_Device->GetDescriptorManager().UseDescriptorIndexing()) + { + // We can't bind textures which are used as color attachments. + const auto& colorAttachments = m_Framebuffer->GetColorAttachments(); + ENSURE(std::find( + colorAttachments.begin(), colorAttachments.end(), textureToBind) == colorAttachments.end()); + ENSURE(m_Framebuffer->GetDepthStencilAttachment() != textureToBind); + + ENSURE(textureToBind->IsInitialized()); + } + + m_ShaderProgram->SetTexture(bindingSlot, textureToBind); +} + +void CDeviceCommandContext::SetUniform( + const int32_t bindingSlot, + const float value) +{ + ENSURE(m_InsidePass); + m_ShaderProgram->SetUniform(bindingSlot, value); +} + +void CDeviceCommandContext::SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY) +{ + ENSURE(m_InsidePass); + m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY); +} + +void CDeviceCommandContext::SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ) +{ + ENSURE(m_InsidePass); + m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ); +} + +void CDeviceCommandContext::SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ, const float valueW) +{ + ENSURE(m_InsidePass); + m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW); +} + +void CDeviceCommandContext::SetUniform( + const int32_t bindingSlot, PS::span values) +{ + ENSURE(m_InsidePass); + m_ShaderProgram->SetUniform(bindingSlot, values); +} + +void CDeviceCommandContext::BeginScopedLabel(const char* name) +{ + if (!m_DebugScopedLabels) + return; + VkDebugUtilsLabelEXT label{}; + label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; + label.pLabelName = name; + vkCmdBeginDebugUtilsLabelEXT(m_CommandContext->GetCommandBuffer(), &label); +} + +void CDeviceCommandContext::EndScopedLabel() +{ + if (!m_DebugScopedLabels) + return; + vkCmdEndDebugUtilsLabelEXT(m_CommandContext->GetCommandBuffer()); +} + +void CDeviceCommandContext::Flush() +{ + ENSURE(!m_InsideFramebufferPass); + // TODO: remove hard-coded values and reduce duplication. + // TODO: fix unsafe copying when overlaping flushes/frames. + + if (m_InPlaceBlockVertexOffset > 0) + { + VkBufferCopy region{}; + region.srcOffset = m_InPlaceBlockIndex * FRAME_INPLACE_BUFFER_SIZE; + region.dstOffset = 0; + region.size = m_InPlaceBlockVertexOffset; + + vkCmdCopyBuffer( + m_PrependCommandContext->GetCommandBuffer(), + m_InPlaceVertexStagingBuffer->GetVkBuffer(), + m_InPlaceVertexBuffer->GetVkBuffer(), 1, ®ion); + } + + if (m_InPlaceBlockIndexOffset > 0) + { + VkBufferCopy region{}; + region.srcOffset = m_InPlaceBlockIndex * FRAME_INPLACE_BUFFER_SIZE; + region.dstOffset = 0; + region.size = m_InPlaceBlockIndexOffset; + vkCmdCopyBuffer( + m_PrependCommandContext->GetCommandBuffer(), + m_InPlaceIndexStagingBuffer->GetVkBuffer(), + m_InPlaceIndexBuffer->GetVkBuffer(), 1, ®ion); + } + + if (m_InPlaceBlockVertexOffset > 0 || m_InPlaceBlockIndexOffset > 0) + { + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + memoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + + vkCmdPipelineBarrier( + m_PrependCommandContext->GetCommandBuffer(), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, + 1, &memoryBarrier, 0, nullptr, 0, nullptr); + } + + if (m_UniformOffset > 0) + { + VkBufferCopy region{}; + // TODO: fix values + region.srcOffset = (m_UniformStagingBuffer->GetSize() / NUMBER_OF_FRAMES_IN_FLIGHT) * m_UniformIndexOffset; + region.dstOffset = 0; + region.size = m_UniformOffset; + vkCmdCopyBuffer( + m_PrependCommandContext->GetCommandBuffer(), + m_UniformStagingBuffer->GetVkBuffer(), + m_UniformBuffer->GetVkBuffer(), 1, ®ion); + m_UniformIndexOffset = (m_UniformIndexOffset + 1) % NUMBER_OF_FRAMES_IN_FLIGHT; + m_UniformOffset = 0; + } + + m_IsPipelineStateDirty = true; + // TODO: maybe move management to CDevice. + m_InPlaceBlockIndex = (m_InPlaceBlockIndex + 1) % NUMBER_OF_FRAMES_IN_FLIGHT; + m_InPlaceBlockVertexOffset = 0; + m_InPlaceBlockIndexOffset = 0; + + m_PrependCommandContext->Flush(); + m_CommandContext->Flush(); +} + +void CDeviceCommandContext::PreDraw() +{ + ENSURE(m_InsidePass); + ApplyPipelineStateIfDirty(); + m_ShaderProgram->PreDraw(m_CommandContext->GetCommandBuffer()); + if (m_ShaderProgram->IsMaterialConstantsDataOutdated()) + { + const VkDeviceSize alignment = + std::max(static_cast(16), m_Device->GetChoosenPhysicalDevice().properties.limits.minUniformBufferOffsetAlignment); + const uint32_t offset = m_UniformOffset + m_UniformIndexOffset * (m_UniformStagingBuffer->GetSize() / NUMBER_OF_FRAMES_IN_FLIGHT); + std::memcpy(static_cast(m_UniformStagingBufferMappedData) + offset, + m_ShaderProgram->GetMaterialConstantsData(), + m_ShaderProgram->GetMaterialConstantsDataSize()); + m_ShaderProgram->UpdateMaterialConstantsData(); + + // TODO: maybe move inside shader program to reduce the # of bind calls. + vkCmdBindDescriptorSets( + m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(), + m_ShaderProgram->GetPipelineLayout(), m_Device->GetDescriptorManager().GetUniformSet(), + 1, &m_UniformDescriptorSet, 1, &m_UniformOffset); + + m_UniformOffset += (m_ShaderProgram->GetMaterialConstantsDataSize() + alignment - 1) & ~(alignment - 1); + } +} + +void CDeviceCommandContext::ApplyPipelineStateIfDirty() +{ + if (!m_IsPipelineStateDirty) + return; + m_IsPipelineStateDirty = false; + + ENSURE(m_GraphicsPipelineState); + ENSURE(m_VertexInputLayout); + ENSURE(m_Framebuffer); + + VkPipeline pipeline = m_GraphicsPipelineState->GetOrCreatePipeline( + m_VertexInputLayout, m_Framebuffer); + ENSURE(pipeline != VK_NULL_HANDLE); + + if (m_LastBoundPipeline != pipeline) + { + m_LastBoundPipeline = pipeline; + vkCmdBindPipeline(m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(), pipeline); + + m_ShaderProgram->Bind(); + + if (m_Device->GetDescriptorManager().UseDescriptorIndexing()) + { + vkCmdBindDescriptorSets( + m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(), + m_ShaderProgram->GetPipelineLayout(), 0, + 1, &m_Device->GetDescriptorManager().GetDescriptorIndexingSet(), 0, nullptr); + } + } +} + +void CDeviceCommandContext::BindVertexBuffer( + const uint32_t bindingSlot, CBuffer* buffer, uint32_t offset) +{ + VkBuffer vertexBuffers[] = { buffer->GetVkBuffer() }; + VkDeviceSize offsets[] = { offset }; + vkCmdBindVertexBuffers( + m_CommandContext->GetCommandBuffer(), bindingSlot, std::size(vertexBuffers), vertexBuffers, offsets); +} + +void CDeviceCommandContext::BindIndexBuffer(CBuffer* buffer, uint32_t offset) +{ + if (buffer == m_BoundIndexBuffer && offset == m_BoundIndexBufferOffset) + return; + m_BoundIndexBuffer = buffer; + m_BoundIndexBufferOffset = offset; + vkCmdBindIndexBuffer( + m_CommandContext->GetCommandBuffer(), buffer->GetVkBuffer(), offset, VK_INDEX_TYPE_UINT16); +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/DeviceSelection.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DeviceSelection.h +++ ps/trunk/source/renderer/backend/vulkan/DeviceSelection.h @@ -0,0 +1,104 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DEVICESELECTION +#define INCLUDED_RENDERER_BACKEND_VULKAN_DEVICESELECTION + +#include "scriptinterface/ScriptForward.h" + +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +/** + * Structure to store all information that might be useful on device selection. + */ +struct SAvailablePhysicalDevice +{ + uint32_t index = std::numeric_limits::max(); + VkPhysicalDevice device = VK_NULL_HANDLE; + VkPhysicalDeviceProperties properties{}; + VkPhysicalDeviceDescriptorIndexingPropertiesEXT descriptorIndexingProperties{}; + VkPhysicalDeviceMemoryProperties memoryProperties{}; + VkPhysicalDeviceFeatures features{}; + VkPhysicalDeviceDescriptorIndexingFeaturesEXT descriptorIndexingFeatures{}; + std::vector queueFamilies; + bool hasRequiredExtensions = false; + bool hasOutputToSurfaceSupport = false; + size_t graphicsQueueFamilyIndex = 0; + size_t presentQueueFamilyIndex = 0; + VkDeviceSize deviceTotalMemory = 0; + VkDeviceSize hostTotalMemory = 0; + std::vector extensions; + VkSurfaceCapabilitiesKHR surfaceCapabilities; + std::vector surfaceFormats; + std::vector presentModes; +}; + +/** + * @return all available physical devices for the Vulkan instance with + * additional flags of surface and required extensions support. + * We could have a single function that returns a selected device. But we use + * multiple functions to be able to save some information about available + * devices before filtering and give a choice to a user. + */ +std::vector GetAvailablePhysicalDevices( + VkInstance instance, VkSurfaceKHR surface, + const std::vector& requiredDeviceExtensions); + +/** + * @return true if we can't use the device for our needs. For example, it + * doesn't graphics or present queues. Because we can't render the game without + * them. + */ +bool IsPhysicalDeviceUnsupported(const SAvailablePhysicalDevice& device); + +/** + * @return true if the first device is better for our needs than the second + * one. Useful in functions like std::sort. The first and the second devices + * should be supported (in other words IsPhysicalDeviceSupported should + * return true for both of them). + */ +bool ComparePhysicalDevices( + const SAvailablePhysicalDevice& device1, + const SAvailablePhysicalDevice& device2); + +bool IsSurfaceFormatSupported( + const VkSurfaceFormatKHR& surfaceFormat); + +/** + * Report all desired information about the available physical device. + */ +void ReportAvailablePhysicalDevice(const SAvailablePhysicalDevice& device, + const ScriptRequest& rq, JS::HandleValue settings); + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_DEVICESELECTION Index: ps/trunk/source/renderer/backend/vulkan/DeviceSelection.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DeviceSelection.cpp +++ ps/trunk/source/renderer/backend/vulkan/DeviceSelection.cpp @@ -0,0 +1,548 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "DeviceSelection.h" + +#include "lib/code_annotation.h" +#include "lib/config2.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Utilities.h" +#include "scriptinterface/JSON.h" +#include "scriptinterface/Object.h" +#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/ScriptRequest.h" + +#include +#include +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace +{ + +std::vector GetPhysicalDeviceExtensions(VkPhysicalDevice device) +{ + uint32_t extensionCount = 0; + ENSURE_VK_SUCCESS(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr)); + std::vector extensions(extensionCount); + ENSURE_VK_SUCCESS(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, extensions.data())); + + std::vector availableExtensions; + availableExtensions.reserve(extensions.size()); + for (const VkExtensionProperties& extension : extensions) + availableExtensions.emplace_back(extension.extensionName); + std::sort(availableExtensions.begin(), availableExtensions.end()); + return availableExtensions; +} + +uint32_t GetDeviceTypeScore(const VkPhysicalDeviceType deviceType) +{ + uint32_t score = 0; + // We prefer discrete GPU over integrated, and integrated over others. + switch (deviceType) + { + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + score = 1; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + score = 4; + break; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + score = 5; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + score = 3; + break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + score = 2; + break; + default: + break; + } + return score; +} + +VkDeviceSize GetDeviceTotalMemory( + const VkPhysicalDeviceMemoryProperties& memoryProperties) +{ + VkDeviceSize totalMemory = 0; + for (uint32_t heapIndex = 0; heapIndex < memoryProperties.memoryHeapCount; ++heapIndex) + if (memoryProperties.memoryHeaps[heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) + totalMemory += memoryProperties.memoryHeaps[heapIndex].size; + return totalMemory; +} + +VkDeviceSize GetHostTotalMemory( + const VkPhysicalDeviceMemoryProperties& memoryProperties) +{ + VkDeviceSize totalMemory = 0; + for (uint32_t heapIndex = 0; heapIndex < memoryProperties.memoryHeapCount; ++heapIndex) + if ((memoryProperties.memoryHeaps[heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0) + totalMemory += memoryProperties.memoryHeaps[heapIndex].size; + return totalMemory; +} + +// We don't support some types in JS, so wrap them to have in the report. +template +struct ReportFormatHelper +{ + std::string operator()(const T&) const { return "unknown"; } +}; + +template +struct ReportFormatHelper>> +{ + float operator()(const T& value) const { return static_cast(value); } +}; + +template +struct ReportFormatHelper>> +{ + static constexpr bool IsSigned = std::is_signed_v; + using ResultType = std::conditional_t; + uint32_t operator()(const T& value) const + { + if (value > std::numeric_limits::max()) + return std::numeric_limits::max(); + if constexpr (IsSigned) + { + if (value < std::numeric_limits::min()) + return std::numeric_limits::min(); + } + return static_cast(value); + } +}; + +template +struct ReportFormatHelper>> +{ + using HelperType = ReportFormatHelper>; + using ResultType = std::invoke_result_t>; + ResultType operator()(const T& value) const + { + HelperType helper{}; + return helper(value); + } +}; + +template +struct ReportFormatHelper>> +{ + using HelperType = ReportFormatHelper>; + using ElementType = std::invoke_result_t>; + std::vector operator()(const T& value) const + { + std::vector arr; + arr.reserve(std::size(value)); + HelperType helper{}; + for (const auto& element : value) + arr.emplace_back(helper(element)); + return arr; + } +}; + +SAvailablePhysicalDevice MakeAvailablePhysicalDevice( + const uint32_t physicalDeviceIndex, VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, const std::vector& requiredDeviceExtensions) +{ + SAvailablePhysicalDevice availablePhysicalDevice{}; + + availablePhysicalDevice.index = physicalDeviceIndex; + availablePhysicalDevice.device = physicalDevice; + availablePhysicalDevice.hasOutputToSurfaceSupport = false; + availablePhysicalDevice.extensions = GetPhysicalDeviceExtensions(availablePhysicalDevice.device); + auto hasExtension = [&extensions = availablePhysicalDevice.extensions](const char* name) -> bool + { + return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); + }; + + availablePhysicalDevice.hasRequiredExtensions = + std::all_of(requiredDeviceExtensions.begin(), requiredDeviceExtensions.end(), hasExtension); + + vkGetPhysicalDeviceMemoryProperties( + availablePhysicalDevice.device, &availablePhysicalDevice.memoryProperties); + availablePhysicalDevice.deviceTotalMemory = + GetDeviceTotalMemory(availablePhysicalDevice.memoryProperties); + availablePhysicalDevice.hostTotalMemory = + GetHostTotalMemory(availablePhysicalDevice.memoryProperties); + + VkPhysicalDeviceDescriptorIndexingPropertiesEXT descriptorIndexingProperties{}; + descriptorIndexingProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 deviesProperties2{}; + deviesProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + deviesProperties2.pNext = &descriptorIndexingProperties; + vkGetPhysicalDeviceProperties2(availablePhysicalDevice.device, &deviesProperties2); + availablePhysicalDevice.properties = deviesProperties2.properties; + availablePhysicalDevice.descriptorIndexingProperties = descriptorIndexingProperties; + + VkPhysicalDeviceDescriptorIndexingFeaturesEXT descriptorIndexingFeatures{}; + descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &descriptorIndexingFeatures; + vkGetPhysicalDeviceFeatures2(availablePhysicalDevice.device, &deviceFeatures2); + availablePhysicalDevice.features = deviceFeatures2.features; + availablePhysicalDevice.descriptorIndexingFeatures = descriptorIndexingFeatures; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(availablePhysicalDevice.device, &queueFamilyCount, nullptr); + availablePhysicalDevice.queueFamilies.resize(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties( + availablePhysicalDevice.device, &queueFamilyCount, availablePhysicalDevice.queueFamilies.data()); + + availablePhysicalDevice.graphicsQueueFamilyIndex = availablePhysicalDevice.queueFamilies.size(); + availablePhysicalDevice.presentQueueFamilyIndex = availablePhysicalDevice.queueFamilies.size(); + for (size_t familyIdx = 0; familyIdx < availablePhysicalDevice.queueFamilies.size(); ++familyIdx) + { + const VkQueueFamilyProperties& queueFamily = availablePhysicalDevice.queueFamilies[familyIdx]; + if (surface != VK_NULL_HANDLE) + { + VkBool32 hasOutputToSurfaceSupport = false; + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceSupportKHR( + availablePhysicalDevice.device, familyIdx, surface, &hasOutputToSurfaceSupport)); + availablePhysicalDevice.hasOutputToSurfaceSupport = hasOutputToSurfaceSupport; + if ((queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) && hasOutputToSurfaceSupport) + { + availablePhysicalDevice.graphicsQueueFamilyIndex = familyIdx; + availablePhysicalDevice.presentQueueFamilyIndex = familyIdx; + } + } + } + + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + availablePhysicalDevice.device, surface, &availablePhysicalDevice.surfaceCapabilities)); + + uint32_t surfaceFormatCount = 0; + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceFormatsKHR( + availablePhysicalDevice.device, surface, &surfaceFormatCount, nullptr)); + if (surfaceFormatCount > 0) + { + availablePhysicalDevice.surfaceFormats.resize(surfaceFormatCount); + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceFormatsKHR( + availablePhysicalDevice.device, surface, &surfaceFormatCount, availablePhysicalDevice.surfaceFormats.data())); + } + + uint32_t presentModeCount = 0; + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfacePresentModesKHR( + availablePhysicalDevice.device, surface, &presentModeCount, nullptr)); + if (presentModeCount > 0) + { + availablePhysicalDevice.presentModes.resize(presentModeCount); + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfacePresentModesKHR( + availablePhysicalDevice.device, surface, &presentModeCount, availablePhysicalDevice.presentModes.data())); + } + + return availablePhysicalDevice; +} + +} // anonymous namespace + +std::vector GetAvailablePhysicalDevices( + VkInstance instance, VkSurfaceKHR surface, + const std::vector& requiredDeviceExtensions) +{ + uint32_t physicalDeviceCount = 0; + ENSURE_VK_SUCCESS(vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr)); + if (physicalDeviceCount == 0) + return {}; + + std::vector availablePhysicalDevices; + availablePhysicalDevices.reserve(physicalDeviceCount); + + std::vector physicalDevices(physicalDeviceCount); + ENSURE_VK_SUCCESS(vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data())); + for (uint32_t physicalDeviceIndex = 0; physicalDeviceIndex < physicalDeviceCount; ++physicalDeviceIndex) + { + availablePhysicalDevices.emplace_back(MakeAvailablePhysicalDevice( + physicalDeviceIndex, physicalDevices[physicalDeviceIndex], surface, requiredDeviceExtensions)); + } + + return availablePhysicalDevices; +} + +bool IsPhysicalDeviceUnsupported(const SAvailablePhysicalDevice& device) +{ + if (!device.hasRequiredExtensions) + return true; + // We can't draw something without graphics queue. And currently we don't + // support separate queues for graphics and present. + if (device.graphicsQueueFamilyIndex != device.presentQueueFamilyIndex) + return true; + if (device.graphicsQueueFamilyIndex == device.queueFamilies.size()) + return true; + if (!device.hasOutputToSurfaceSupport) + return true; + if (device.properties.limits.maxBoundDescriptorSets < 4) + return true; + // It's guaranteed to have sRGB but we don't support it yet. + return std::none_of(device.surfaceFormats.begin(), device.surfaceFormats.end(), IsSurfaceFormatSupported); +} + +bool ComparePhysicalDevices( + const SAvailablePhysicalDevice& device1, + const SAvailablePhysicalDevice& device2) +{ + const uint32_t deviceTypeScore1 = GetDeviceTypeScore(device1.properties.deviceType); + const uint32_t deviceTypeScore2 = GetDeviceTypeScore(device2.properties.deviceType); + if (deviceTypeScore1 != deviceTypeScore2) + return deviceTypeScore1 > deviceTypeScore2; + // We use a total device memory amount to compare. We assume that more memory + // means better performance as previous metrics are equal. + return device1.deviceTotalMemory > device2.deviceTotalMemory; +} + +bool IsSurfaceFormatSupported( + const VkSurfaceFormatKHR& surfaceFormat) +{ + return + surfaceFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR && + (surfaceFormat.format == VK_FORMAT_R8G8B8A8_UNORM || + surfaceFormat.format == VK_FORMAT_B8G8R8A8_UNORM); +} + +void ReportAvailablePhysicalDevice(const SAvailablePhysicalDevice& device, + const ScriptRequest& rq, JS::HandleValue settings) +{ + Script::SetProperty(rq, settings, "name", device.properties.deviceName); + Script::SetProperty(rq, settings, "version", + std::to_string(VK_API_VERSION_VARIANT(device.properties.apiVersion)) + + "." + std::to_string(VK_API_VERSION_MAJOR(device.properties.apiVersion)) + + "." + std::to_string(VK_API_VERSION_MINOR(device.properties.apiVersion)) + + "." + std::to_string(VK_API_VERSION_PATCH(device.properties.apiVersion))); + Script::SetProperty(rq, settings, "apiVersion", device.properties.apiVersion); + Script::SetProperty(rq, settings, "driverVersion", device.properties.driverVersion); + Script::SetProperty(rq, settings, "vendorID", device.properties.vendorID); + Script::SetProperty(rq, settings, "deviceID", device.properties.deviceID); + Script::SetProperty(rq, settings, "deviceType", static_cast(device.properties.deviceType)); + Script::SetProperty(rq, settings, "index", device.index); + + JS::RootedValue memory(rq.cx); + Script::CreateObject(rq, &memory); + + JS::RootedValue memoryTypes(rq.cx); + Script::CreateArray(rq, &memoryTypes, device.memoryProperties.memoryTypeCount); + for (uint32_t memoryTypeIndex = 0; memoryTypeIndex < device.memoryProperties.memoryTypeCount; ++memoryTypeIndex) + { + const VkMemoryType& type = device.memoryProperties.memoryTypes[memoryTypeIndex]; + JS::RootedValue memoryType(rq.cx); + Script::CreateObject(rq, &memoryType); + Script::SetProperty(rq, memoryType, "propertyFlags", static_cast(type.propertyFlags)); + Script::SetProperty(rq, memoryType, "heapIndex", type.heapIndex); + Script::SetPropertyInt(rq, memoryTypes, memoryTypeIndex, memoryType); + } + JS::RootedValue memoryHeaps(rq.cx); + Script::CreateArray(rq, &memoryHeaps, device.memoryProperties.memoryHeapCount); + for (uint32_t memoryHeapIndex = 0; memoryHeapIndex < device.memoryProperties.memoryHeapCount; ++memoryHeapIndex) + { + const VkMemoryHeap& heap = device.memoryProperties.memoryHeaps[memoryHeapIndex]; + JS::RootedValue memoryHeap(rq.cx); + Script::CreateObject(rq, &memoryHeap); + // We can't serialize uint64_t in JS, so put data in KiB. + Script::SetProperty(rq, memoryHeap, "size", static_cast(heap.size / 1024)); + Script::SetProperty(rq, memoryHeap, "flags", static_cast(heap.flags)); + Script::SetPropertyInt(rq, memoryHeaps, memoryHeapIndex, memoryHeap); + } + + Script::SetProperty(rq, memory, "types", memoryTypes); + Script::SetProperty(rq, memory, "heaps", memoryHeaps); + Script::SetProperty(rq, settings, "memory", memory); + + JS::RootedValue constants(rq.cx); + Script::CreateObject(rq, &constants); + + JS::RootedValue limitsConstants(rq.cx); + Script::CreateObject(rq, &limitsConstants); +#define REPORT_LIMITS_CONSTANT(NAME) \ + do \ + { \ + const ReportFormatHelper helper{}; \ + Script::SetProperty(rq, limitsConstants, #NAME, helper(device.properties.limits.NAME)); \ + } while (0) + REPORT_LIMITS_CONSTANT(maxImageDimension1D); + REPORT_LIMITS_CONSTANT(maxImageDimension2D); + REPORT_LIMITS_CONSTANT(maxImageDimension3D); + REPORT_LIMITS_CONSTANT(maxImageDimensionCube); + REPORT_LIMITS_CONSTANT(maxImageArrayLayers); + REPORT_LIMITS_CONSTANT(maxUniformBufferRange); + REPORT_LIMITS_CONSTANT(maxStorageBufferRange); + REPORT_LIMITS_CONSTANT(maxPushConstantsSize); + REPORT_LIMITS_CONSTANT(maxMemoryAllocationCount); + REPORT_LIMITS_CONSTANT(maxSamplerAllocationCount); + REPORT_LIMITS_CONSTANT(bufferImageGranularity); + REPORT_LIMITS_CONSTANT(maxBoundDescriptorSets); + REPORT_LIMITS_CONSTANT(maxPerStageDescriptorSamplers); + REPORT_LIMITS_CONSTANT(maxPerStageDescriptorUniformBuffers); + REPORT_LIMITS_CONSTANT(maxPerStageDescriptorStorageBuffers); + REPORT_LIMITS_CONSTANT(maxPerStageDescriptorSampledImages); + REPORT_LIMITS_CONSTANT(maxPerStageDescriptorStorageImages); + REPORT_LIMITS_CONSTANT(maxPerStageDescriptorInputAttachments); + REPORT_LIMITS_CONSTANT(maxPerStageResources); + REPORT_LIMITS_CONSTANT(maxDescriptorSetSamplers); + REPORT_LIMITS_CONSTANT(maxDescriptorSetUniformBuffers); + REPORT_LIMITS_CONSTANT(maxDescriptorSetUniformBuffersDynamic); + REPORT_LIMITS_CONSTANT(maxDescriptorSetStorageBuffers); + REPORT_LIMITS_CONSTANT(maxDescriptorSetStorageBuffersDynamic); + REPORT_LIMITS_CONSTANT(maxDescriptorSetSampledImages); + REPORT_LIMITS_CONSTANT(maxDescriptorSetStorageImages); + REPORT_LIMITS_CONSTANT(maxDescriptorSetInputAttachments); + REPORT_LIMITS_CONSTANT(maxVertexInputAttributes); + REPORT_LIMITS_CONSTANT(maxVertexInputBindings); + REPORT_LIMITS_CONSTANT(maxVertexInputAttributeOffset); + REPORT_LIMITS_CONSTANT(maxVertexInputBindingStride); + REPORT_LIMITS_CONSTANT(maxComputeSharedMemorySize); + REPORT_LIMITS_CONSTANT(maxComputeWorkGroupCount); + REPORT_LIMITS_CONSTANT(maxComputeWorkGroupInvocations); + REPORT_LIMITS_CONSTANT(maxComputeWorkGroupSize); + REPORT_LIMITS_CONSTANT(maxDrawIndexedIndexValue); + REPORT_LIMITS_CONSTANT(maxSamplerLodBias); + REPORT_LIMITS_CONSTANT(maxSamplerAnisotropy); + REPORT_LIMITS_CONSTANT(minMemoryMapAlignment); + REPORT_LIMITS_CONSTANT(minTexelBufferOffsetAlignment); + REPORT_LIMITS_CONSTANT(minUniformBufferOffsetAlignment); + REPORT_LIMITS_CONSTANT(minStorageBufferOffsetAlignment); + REPORT_LIMITS_CONSTANT(maxFramebufferWidth); + REPORT_LIMITS_CONSTANT(maxFramebufferHeight); + REPORT_LIMITS_CONSTANT(maxFramebufferLayers); + REPORT_LIMITS_CONSTANT(framebufferColorSampleCounts); + REPORT_LIMITS_CONSTANT(framebufferDepthSampleCounts); + REPORT_LIMITS_CONSTANT(framebufferStencilSampleCounts); + REPORT_LIMITS_CONSTANT(framebufferNoAttachmentsSampleCounts); + REPORT_LIMITS_CONSTANT(maxColorAttachments); + REPORT_LIMITS_CONSTANT(sampledImageColorSampleCounts); + REPORT_LIMITS_CONSTANT(sampledImageDepthSampleCounts); + REPORT_LIMITS_CONSTANT(sampledImageStencilSampleCounts); + REPORT_LIMITS_CONSTANT(storageImageSampleCounts); + REPORT_LIMITS_CONSTANT(optimalBufferCopyOffsetAlignment); + REPORT_LIMITS_CONSTANT(optimalBufferCopyRowPitchAlignment); +#undef REPORT_LIMITS_CONSTANT + Script::SetProperty(rq, constants, "limits", limitsConstants); + + JS::RootedValue descriptorIndexingConstants(rq.cx); + Script::CreateObject(rq, &descriptorIndexingConstants); +#define REPORT_DESCRIPTOR_INDEXING_CONSTANT(NAME) \ + do \ + { \ + const ReportFormatHelper helper{}; \ + Script::SetProperty(rq, descriptorIndexingConstants, #NAME, helper(device.descriptorIndexingProperties.NAME)); \ + } while (0) + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxUpdateAfterBindDescriptorsInAllPools); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(shaderSampledImageArrayNonUniformIndexingNative); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxPerStageDescriptorUpdateAfterBindSamplers); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxPerStageDescriptorUpdateAfterBindSampledImages); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxPerStageDescriptorUpdateAfterBindUniformBuffers); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxPerStageUpdateAfterBindResources); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxDescriptorSetUpdateAfterBindSamplers); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxDescriptorSetUpdateAfterBindSampledImages); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxDescriptorSetUpdateAfterBindUniformBuffers); + REPORT_DESCRIPTOR_INDEXING_CONSTANT(maxDescriptorSetUpdateAfterBindUniformBuffersDynamic); +#undef REPORT_DESCRIPTOR_INDEXING_CONSTANT + Script::SetProperty(rq, constants, "descriptor_indexing", descriptorIndexingConstants); + + Script::SetProperty(rq, settings, "constants", constants); + + JS::RootedValue features(rq.cx); + Script::CreateObject(rq, &features); +#define REPORT_FEATURE(NAME) \ + Script::SetProperty(rq, features, #NAME, static_cast(device.features.NAME)); + REPORT_FEATURE(imageCubeArray); + REPORT_FEATURE(geometryShader); + REPORT_FEATURE(tessellationShader); + REPORT_FEATURE(logicOp); + REPORT_FEATURE(multiDrawIndirect); + REPORT_FEATURE(depthClamp); + REPORT_FEATURE(depthBiasClamp); + REPORT_FEATURE(samplerAnisotropy); + REPORT_FEATURE(textureCompressionETC2); + REPORT_FEATURE(textureCompressionASTC_LDR); + REPORT_FEATURE(textureCompressionBC); + REPORT_FEATURE(pipelineStatisticsQuery); +#undef REPORT_FEATURE + +#define REPORT_DESCRIPTOR_INDEXING_FEATURE(NAME) \ + Script::SetProperty(rq, features, #NAME, static_cast(device.descriptorIndexingFeatures.NAME)); + REPORT_DESCRIPTOR_INDEXING_FEATURE(shaderSampledImageArrayNonUniformIndexing); + REPORT_DESCRIPTOR_INDEXING_FEATURE(descriptorBindingUniformBufferUpdateAfterBind); + REPORT_DESCRIPTOR_INDEXING_FEATURE(descriptorBindingSampledImageUpdateAfterBind); + REPORT_DESCRIPTOR_INDEXING_FEATURE(descriptorBindingPartiallyBound); + REPORT_DESCRIPTOR_INDEXING_FEATURE(descriptorBindingUpdateUnusedWhilePending); + REPORT_DESCRIPTOR_INDEXING_FEATURE(descriptorBindingPartiallyBound); + REPORT_DESCRIPTOR_INDEXING_FEATURE(descriptorBindingVariableDescriptorCount); + REPORT_DESCRIPTOR_INDEXING_FEATURE(runtimeDescriptorArray); +#undef REPORT_DESCRIPTOR_INDEXING_FEATURE + + Script::SetProperty(rq, settings, "features", features); + + JS::RootedValue presentModes(rq.cx); + Script::CreateArray(rq, &presentModes, device.presentModes.size()); + for (size_t index = 0; index < device.presentModes.size(); ++index) + { + Script::SetPropertyInt( + rq, presentModes, index, static_cast(device.presentModes[index])); + } + Script::SetProperty(rq, settings, "present_modes", presentModes); + + JS::RootedValue surfaceFormats(rq.cx); + Script::CreateArray(rq, &surfaceFormats, device.surfaceFormats.size()); + for (size_t index = 0; index < device.surfaceFormats.size(); ++index) + { + JS::RootedValue surfaceFormat(rq.cx); + Script::CreateObject(rq, &surfaceFormat); + Script::SetProperty( + rq, surfaceFormat, "format", static_cast(device.surfaceFormats[index].format)); + Script::SetProperty( + rq, surfaceFormat, "color_space", static_cast(device.surfaceFormats[index].colorSpace)); + Script::SetPropertyInt(rq, surfaceFormats, index, surfaceFormat); + } + Script::SetProperty(rq, settings, "surface_formats", surfaceFormats); + + JS::RootedValue surfaceCapabilities(rq.cx); + Script::CreateObject(rq, &surfaceCapabilities); +#define REPORT_SURFACE_CAPABILITIES_CONSTANT(NAME) \ + do \ + { \ + const ReportFormatHelper helper{}; \ + Script::SetProperty(rq, surfaceCapabilities, #NAME, helper(device.surfaceCapabilities.NAME)); \ + } while (0) + REPORT_SURFACE_CAPABILITIES_CONSTANT(minImageCount); + REPORT_SURFACE_CAPABILITIES_CONSTANT(maxImageCount); + REPORT_SURFACE_CAPABILITIES_CONSTANT(maxImageArrayLayers); + REPORT_SURFACE_CAPABILITIES_CONSTANT(supportedTransforms); + REPORT_SURFACE_CAPABILITIES_CONSTANT(supportedCompositeAlpha); + REPORT_SURFACE_CAPABILITIES_CONSTANT(supportedUsageFlags); +#undef REPORT_SURFACE_CAPABILITIES_CONSTANT + Script::SetProperty(rq, settings, "surface_capabilities", surfaceCapabilities); +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Framebuffer.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Framebuffer.h +++ ps/trunk/source/renderer/backend/vulkan/Framebuffer.h @@ -0,0 +1,110 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_FRAMEBUFFER +#define INCLUDED_RENDERER_BACKEND_VULKAN_FRAMEBUFFER + +#include "ps/containers/StaticVector.h" +#include "renderer/backend/IFramebuffer.h" + +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; +class CTexture; + +class CFramebuffer final : public IFramebuffer +{ +public: + ~CFramebuffer() override; + + IDevice* GetDevice() override; + + const CColor& GetClearColor() const override { return m_ClearColor; } + + uint32_t GetWidth() const override { return m_Width; } + uint32_t GetHeight() const override { return m_Height; } + uint32_t GetSampleCount() const { return m_SampleCount; } + + VkRenderPass GetRenderPass() const { return m_RenderPass; } + VkFramebuffer GetFramebuffer() const { return m_Framebuffer; } + + const PS::StaticVector& GetColorAttachments() { return m_ColorAttachments; } + CTexture* GetDepthStencilAttachment() { return m_DepthStencilAttachment; } + + AttachmentLoadOp GetColorAttachmentLoadOp() const { return m_ColorAttachmentLoadOp; } + AttachmentStoreOp GetColorAttachmentStoreOp() const { return m_ColorAttachmentStoreOp; } + AttachmentLoadOp GetDepthStencilAttachmentLoadOp() const { return m_DepthStencilAttachmentLoadOp; } + AttachmentStoreOp GetDepthStencilAttachmentStoreOp() const { return m_DepthStencilAttachmentStoreOp; } + + using UID = uint32_t; + UID GetUID() const { return m_UID; } + +private: + friend class CDevice; + friend class CSwapChain; + + static std::unique_ptr Create( + CDevice* device, const char* name, + SColorAttachment* colorAttachment, SDepthStencilAttachment* depthStencilAttachment); + + CFramebuffer() + { + static uint32_t m_LastAvailableUID = 1; + m_UID = m_LastAvailableUID++; + } + + CDevice* m_Device = nullptr; + + UID m_UID = 0; + + CColor m_ClearColor{}; + + uint32_t m_Width = 0; + uint32_t m_Height = 0; + uint32_t m_SampleCount = 0; + + AttachmentLoadOp m_ColorAttachmentLoadOp = AttachmentLoadOp::DONT_CARE; + AttachmentStoreOp m_ColorAttachmentStoreOp = AttachmentStoreOp::DONT_CARE; + AttachmentLoadOp m_DepthStencilAttachmentLoadOp = AttachmentLoadOp::DONT_CARE; + AttachmentStoreOp m_DepthStencilAttachmentStoreOp = AttachmentStoreOp::DONT_CARE; + + VkRenderPass m_RenderPass = VK_NULL_HANDLE; + VkFramebuffer m_Framebuffer = VK_NULL_HANDLE; + + // It's reponsibility of CFramebuffer owner to guarantee lifetime of + // attachments. + PS::StaticVector m_ColorAttachments; + CTexture* m_DepthStencilAttachment = nullptr; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_FRAMEBUFFER Index: ps/trunk/source/renderer/backend/vulkan/Framebuffer.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Framebuffer.cpp +++ ps/trunk/source/renderer/backend/vulkan/Framebuffer.cpp @@ -0,0 +1,129 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "Framebuffer.h" + +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/RenderPassManager.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/Utilities.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +// static +std::unique_ptr CFramebuffer::Create( + CDevice* device, const char* name, + SColorAttachment* colorAttachment, SDepthStencilAttachment* depthStencilAttachment) +{ + ENSURE(colorAttachment || depthStencilAttachment); + + if (colorAttachment && depthStencilAttachment) + { + CTexture* colorAttachmentTexture = colorAttachment->texture->As(); + CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As(); + ENSURE( + colorAttachmentTexture->GetWidth() == depthStencilAttachmentTexture->GetWidth() && + colorAttachmentTexture->GetHeight() == depthStencilAttachmentTexture->GetHeight() && + colorAttachmentTexture->GetSampleCount() == depthStencilAttachmentTexture->GetSampleCount()); + } + + std::unique_ptr framebuffer(new CFramebuffer()); + framebuffer->m_Device = device; + if (colorAttachment) + framebuffer->m_ClearColor = colorAttachment->clearColor; + + PS::StaticVector attachments; + + if (colorAttachment) + { + CTexture* colorAttachmentTexture = colorAttachment->texture->As(); + + framebuffer->m_Width = colorAttachmentTexture->GetWidth(); + framebuffer->m_Height = colorAttachmentTexture->GetHeight(); + framebuffer->m_SampleCount = colorAttachmentTexture->GetSampleCount(); + framebuffer->m_ColorAttachmentLoadOp = colorAttachment->loadOp; + framebuffer->m_ColorAttachmentStoreOp = colorAttachment->storeOp; + + attachments.emplace_back(colorAttachmentTexture->GetAttachmentImageView()); + framebuffer->m_ColorAttachments.emplace_back(colorAttachmentTexture); + } + + if (depthStencilAttachment) + { + CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As(); + + framebuffer->m_Width = depthStencilAttachmentTexture->GetWidth(); + framebuffer->m_Height = depthStencilAttachmentTexture->GetHeight(); + framebuffer->m_SampleCount = depthStencilAttachmentTexture->GetSampleCount(); + + framebuffer->m_DepthStencilAttachmentLoadOp = depthStencilAttachment->loadOp; + framebuffer->m_DepthStencilAttachmentStoreOp = depthStencilAttachment->storeOp; + + attachments.emplace_back(depthStencilAttachmentTexture->GetAttachmentImageView()); + framebuffer->m_DepthStencilAttachment = depthStencilAttachmentTexture; + } + + ENSURE(framebuffer->m_Width > 0 && framebuffer->m_Height > 0); + ENSURE(framebuffer->m_SampleCount > 0); + + framebuffer->m_RenderPass = device->GetRenderPassManager().GetOrCreateRenderPass( + colorAttachment, depthStencilAttachment); + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = framebuffer->m_RenderPass; + framebufferInfo.attachmentCount = attachments.size(); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = framebuffer->m_Width; + framebufferInfo.height = framebuffer->m_Height; + framebufferInfo.layers = 1; + + ENSURE_VK_SUCCESS(vkCreateFramebuffer( + device->GetVkDevice(), &framebufferInfo, nullptr, &framebuffer->m_Framebuffer)); + + device->SetObjectName(VK_OBJECT_TYPE_FRAMEBUFFER, framebuffer->m_Framebuffer, name); + + return framebuffer; +} + +CFramebuffer::~CFramebuffer() +{ + if (m_Framebuffer != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_FRAMEBUFFER, m_Framebuffer, VK_NULL_HANDLE); +} + +IDevice* CFramebuffer::GetDevice() +{ + return m_Device; +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Mapping.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Mapping.h +++ ps/trunk/source/renderer/backend/vulkan/Mapping.h @@ -0,0 +1,72 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING +#define INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING + +#include "renderer/backend/Format.h" +#include "renderer/backend/IFramebuffer.h" +#include "renderer/backend/PipelineState.h" +#include "renderer/backend/Sampler.h" + +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace Mapping +{ + +VkCompareOp FromCompareOp(const CompareOp compareOp); + +VkStencilOp FromStencilOp(const StencilOp stencilOp); + +VkBlendFactor FromBlendFactor(const BlendFactor blendFactor); + +VkBlendOp FromBlendOp(const BlendOp blendOp); + +VkColorComponentFlags FromColorWriteMask(const uint32_t colorWriteMask); + +VkPolygonMode FromPolygonMode(const PolygonMode polygonMode); + +VkCullModeFlags FromCullMode(const CullMode cullMode); + +VkFormat FromFormat(const Format format); + +VkSampleCountFlagBits FromSampleCount(const uint32_t sampleCount); + +VkSamplerAddressMode FromAddressMode(const Sampler::AddressMode addressMode); + +VkAttachmentLoadOp FromAttachmentLoadOp(const AttachmentLoadOp loadOp); + +VkAttachmentStoreOp FromAttachmentStoreOp(const AttachmentStoreOp storeOp); + +} // namespace Mapping + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_MAPPING Index: ps/trunk/source/renderer/backend/vulkan/Mapping.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Mapping.cpp +++ ps/trunk/source/renderer/backend/vulkan/Mapping.cpp @@ -0,0 +1,280 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "Mapping.h" + +#include "lib/code_annotation.h" +#include "lib/config2.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace Mapping +{ + +VkCompareOp FromCompareOp(const CompareOp compareOp) +{ + VkCompareOp op = VK_COMPARE_OP_NEVER; + switch (compareOp) + { +#define CASE(NAME) case CompareOp::NAME: op = VK_COMPARE_OP_##NAME; break + CASE(NEVER); + CASE(LESS); + CASE(EQUAL); + CASE(LESS_OR_EQUAL); + CASE(GREATER); + CASE(NOT_EQUAL); + CASE(GREATER_OR_EQUAL); + CASE(ALWAYS); +#undef CASE + } + return op; +} + +VkStencilOp FromStencilOp(const StencilOp stencilOp) +{ + VkStencilOp op = VK_STENCIL_OP_KEEP; + switch (stencilOp) + { +#define CASE(NAME) case StencilOp::NAME: op = VK_STENCIL_OP_##NAME; break + CASE(KEEP); + CASE(ZERO); + CASE(REPLACE); + CASE(INCREMENT_AND_CLAMP); + CASE(DECREMENT_AND_CLAMP); + CASE(INVERT); + CASE(INCREMENT_AND_WRAP); + CASE(DECREMENT_AND_WRAP); +#undef CASE + } + return op; +} + +VkBlendFactor FromBlendFactor(const BlendFactor blendFactor) +{ + VkBlendFactor factor = VK_BLEND_FACTOR_ZERO; + switch (blendFactor) + { +#define CASE(NAME) case BlendFactor::NAME: factor = VK_BLEND_FACTOR_##NAME; break + CASE(ZERO); + CASE(ONE); + CASE(SRC_COLOR); + CASE(ONE_MINUS_SRC_COLOR); + CASE(DST_COLOR); + CASE(ONE_MINUS_DST_COLOR); + CASE(SRC_ALPHA); + CASE(ONE_MINUS_SRC_ALPHA); + CASE(DST_ALPHA); + CASE(ONE_MINUS_DST_ALPHA); + CASE(CONSTANT_COLOR); + CASE(ONE_MINUS_CONSTANT_COLOR); + CASE(CONSTANT_ALPHA); + CASE(ONE_MINUS_CONSTANT_ALPHA); + CASE(SRC_ALPHA_SATURATE); + + CASE(SRC1_COLOR); + CASE(ONE_MINUS_SRC1_COLOR); + CASE(SRC1_ALPHA); + CASE(ONE_MINUS_SRC1_ALPHA); +#undef CASE + } + return factor; +} + +VkBlendOp FromBlendOp(const BlendOp blendOp) +{ + VkBlendOp mode = VK_BLEND_OP_ADD; + switch (blendOp) + { + case BlendOp::ADD: mode = VK_BLEND_OP_ADD; break; + case BlendOp::SUBTRACT: mode = VK_BLEND_OP_SUBTRACT; break; + case BlendOp::REVERSE_SUBTRACT: mode = VK_BLEND_OP_REVERSE_SUBTRACT; break; + case BlendOp::MIN: mode = VK_BLEND_OP_MIN; break; + case BlendOp::MAX: mode = VK_BLEND_OP_MAX; break; + }; + return mode; +} + +VkColorComponentFlags FromColorWriteMask(const uint32_t colorWriteMask) +{ + VkColorComponentFlags flags = 0; + if (colorWriteMask & ColorWriteMask::RED) + flags |= VK_COLOR_COMPONENT_R_BIT; + if (colorWriteMask & ColorWriteMask::GREEN) + flags |= VK_COLOR_COMPONENT_G_BIT; + if (colorWriteMask & ColorWriteMask::BLUE) + flags |= VK_COLOR_COMPONENT_B_BIT; + if (colorWriteMask & ColorWriteMask::ALPHA) + flags |= VK_COLOR_COMPONENT_A_BIT; + return flags; +} + +VkPolygonMode FromPolygonMode(const PolygonMode polygonMode) +{ + if (polygonMode == PolygonMode::LINE) + return VK_POLYGON_MODE_LINE; + return VK_POLYGON_MODE_FILL; +} + +VkCullModeFlags FromCullMode(const CullMode cullMode) +{ + VkCullModeFlags flags = VK_CULL_MODE_NONE; + switch (cullMode) + { + case CullMode::NONE: + break; + case CullMode::FRONT: + flags |= VK_CULL_MODE_FRONT_BIT; + break; + case CullMode::BACK: + flags |= VK_CULL_MODE_BACK_BIT; + break; + } + return flags; +} + +VkFormat FromFormat(const Format format) +{ + VkFormat resultFormat = VK_FORMAT_UNDEFINED; + switch (format) + { +#define CASE(NAME) case Format::NAME: resultFormat = VK_FORMAT_##NAME; break; +#define CASE2(NAME, VK_NAME) case Format::NAME: resultFormat = VK_FORMAT_##VK_NAME; break; + + CASE(UNDEFINED) + + CASE(R8_UNORM) + CASE(R8G8_UNORM) + CASE(R8G8_UINT) + CASE(R8G8B8A8_UNORM) + CASE(R8G8B8A8_UINT) + + CASE(R16_UNORM) + CASE(R16_UINT) + CASE(R16_SINT) + CASE(R16G16_UNORM) + CASE(R16G16_UINT) + CASE(R16G16_SINT) + + CASE(R32_SFLOAT) + CASE(R32G32_SFLOAT) + CASE(R32G32B32_SFLOAT) + CASE(R32G32B32A32_SFLOAT) + + CASE2(D16, D16_UNORM) + CASE2(D24, X8_D24_UNORM_PACK32) + CASE2(D24_S8, D24_UNORM_S8_UINT) + CASE2(D32, D32_SFLOAT) + + CASE2(BC1_RGB_UNORM, BC1_RGB_UNORM_BLOCK) + CASE2(BC1_RGBA_UNORM, BC1_RGBA_UNORM_BLOCK) + CASE2(BC2_UNORM, BC2_UNORM_BLOCK) + CASE2(BC3_UNORM, BC3_UNORM_BLOCK) + +#undef CASE +#undef CASE2 + default: + debug_warn("Unsupported format"); + } + return resultFormat; +} + +VkSampleCountFlagBits FromSampleCount(const uint32_t sampleCount) +{ + VkSampleCountFlagBits flags = VK_SAMPLE_COUNT_1_BIT; + switch (sampleCount) + { + case 1: flags = VK_SAMPLE_COUNT_1_BIT; break; + case 2: flags = VK_SAMPLE_COUNT_2_BIT; break; + case 4: flags = VK_SAMPLE_COUNT_4_BIT; break; + case 8: flags = VK_SAMPLE_COUNT_8_BIT; break; + case 16: flags = VK_SAMPLE_COUNT_16_BIT; break; + default: + debug_warn("Unsupported number of samples"); + } + return flags; +} + +VkSamplerAddressMode FromAddressMode(const Sampler::AddressMode addressMode) +{ + VkSamplerAddressMode resultAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT; + switch (addressMode) + { + case Sampler::AddressMode::REPEAT: + resultAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT; + break; + case Sampler::AddressMode::MIRRORED_REPEAT: + resultAddressMode = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + break; + case Sampler::AddressMode::CLAMP_TO_EDGE: + resultAddressMode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + break; + case Sampler::AddressMode::CLAMP_TO_BORDER: + resultAddressMode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + break; + } + return resultAddressMode; +} + +VkAttachmentLoadOp FromAttachmentLoadOp(const AttachmentLoadOp loadOp) +{ + VkAttachmentLoadOp resultLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + switch (loadOp) + { + case AttachmentLoadOp::LOAD: + resultLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case AttachmentLoadOp::CLEAR: + resultLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case AttachmentLoadOp::DONT_CARE: + resultLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } + return resultLoadOp; +} + +VkAttachmentStoreOp FromAttachmentStoreOp(const AttachmentStoreOp storeOp) +{ + VkAttachmentStoreOp resultStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + switch (storeOp) + { + case AttachmentStoreOp::STORE: + resultStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + break; + case AttachmentStoreOp::DONT_CARE: + resultStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + break; + } + return resultStoreOp; +} + +} // namespace Mapping + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/PipelineState.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/PipelineState.h +++ ps/trunk/source/renderer/backend/vulkan/PipelineState.h @@ -0,0 +1,100 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_PIPELINESTATE +#define INCLUDED_RENDERER_BACKEND_VULKAN_PIPELINESTATE + +#include "renderer/backend/PipelineState.h" +#include "renderer/backend/vulkan/Framebuffer.h" +#include "renderer/backend/vulkan/ShaderProgram.h" + +#include +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; +class CFramebuffer; + +class CGraphicsPipelineState final : public IGraphicsPipelineState +{ +public: + ~CGraphicsPipelineState() override; + + IDevice* GetDevice() override; + + IShaderProgram* GetShaderProgram() const override { return m_Desc.shaderProgram; } + + const SGraphicsPipelineStateDesc& GetDesc() const { return m_Desc; } + + VkPipeline GetOrCreatePipeline( + const CVertexInputLayout* vertexInputLayout, CFramebuffer* framebuffer); + + using UID = uint32_t; + UID GetUID() const { return m_UID; } + +private: + friend class CDevice; + + static std::unique_ptr Create( + CDevice* device, const SGraphicsPipelineStateDesc& desc); + + CGraphicsPipelineState() + { + static uint32_t m_LastAvailableUID = 1; + m_UID = m_LastAvailableUID++; + } + + CDevice* m_Device = nullptr; + + UID m_UID = 0; + + SGraphicsPipelineStateDesc m_Desc{}; + + struct CacheKey + { + CVertexInputLayout::UID vertexInputLayoutUID; + // TODO: try to replace the UID by the only required parameters. + CFramebuffer::UID framebufferUID; + }; + struct CacheKeyHash + { + size_t operator()(const CacheKey& cacheKey) const; + }; + struct CacheKeyEqual + { + bool operator()(const CacheKey& lhs, const CacheKey& rhs) const; + }; + std::unordered_map m_PipelineMap; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_PIPELINESTATE Index: ps/trunk/source/renderer/backend/vulkan/PipelineState.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/PipelineState.cpp +++ ps/trunk/source/renderer/backend/vulkan/PipelineState.cpp @@ -0,0 +1,282 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "PipelineState.h" + +#include "lib/hash.h" +#include "ps/CLogger.h" +#include "ps/containers/StaticVector.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Framebuffer.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/ShaderProgram.h" +#include "renderer/backend/vulkan/Utilities.h" + +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +size_t CGraphicsPipelineState::CacheKeyHash::operator()(const CacheKey& cacheKey) const +{ + size_t seed = 0; + hash_combine(seed, cacheKey.vertexInputLayoutUID); + hash_combine(seed, cacheKey.framebufferUID); + return seed; +} + +bool CGraphicsPipelineState::CacheKeyEqual::operator()(const CacheKey& lhs, const CacheKey& rhs) const +{ + return + lhs.vertexInputLayoutUID == rhs.vertexInputLayoutUID && + lhs.framebufferUID == rhs.framebufferUID; +} + +// static +std::unique_ptr CGraphicsPipelineState::Create( + CDevice* device, const SGraphicsPipelineStateDesc& desc) +{ + ENSURE(desc.shaderProgram); + std::unique_ptr pipelineState{new CGraphicsPipelineState()}; + pipelineState->m_Device = device; + pipelineState->m_Desc = desc; + return pipelineState; +} + +CGraphicsPipelineState::~CGraphicsPipelineState() +{ + for (const auto& it : m_PipelineMap) + { + if (it.second != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_PIPELINE, it.second, VK_NULL_HANDLE); + } +} + +VkPipeline CGraphicsPipelineState::GetOrCreatePipeline( + const CVertexInputLayout* vertexInputLayout, CFramebuffer* framebuffer) +{ + CShaderProgram* shaderProgram = m_Desc.shaderProgram->As(); + + const CacheKey cacheKey = + { + vertexInputLayout->GetUID(), framebuffer->GetUID() + }; + auto it = m_PipelineMap.find(cacheKey); + if (it != m_PipelineMap.end()) + return it->second; + + PS::StaticVector attributeBindings; + PS::StaticVector attributes; + + const VkPhysicalDeviceLimits& limits = m_Device->GetChoosenPhysicalDevice().properties.limits; + const uint32_t maxVertexInputAttributes = limits.maxVertexInputAttributes; + const uint32_t maxVertexInputAttributeOffset = limits.maxVertexInputAttributeOffset; + for (const SVertexAttributeFormat& vertexAttributeFormat : vertexInputLayout->GetAttributes()) + { + ENSURE(vertexAttributeFormat.bindingSlot < maxVertexInputAttributes); + ENSURE(vertexAttributeFormat.offset < maxVertexInputAttributeOffset); + const uint32_t streamLocation = shaderProgram->GetStreamLocation(vertexAttributeFormat.stream); + if (streamLocation == std::numeric_limits::max()) + continue; + auto it = std::find_if(attributeBindings.begin(), attributeBindings.end(), + [slot = vertexAttributeFormat.bindingSlot](const VkVertexInputBindingDescription& desc) -> bool + { + return desc.binding == slot; + }); + const VkVertexInputBindingDescription desc{ + vertexAttributeFormat.bindingSlot, + vertexAttributeFormat.stride, + vertexAttributeFormat.rate == VertexAttributeRate::PER_INSTANCE + ? VK_VERTEX_INPUT_RATE_INSTANCE + : VK_VERTEX_INPUT_RATE_VERTEX }; + if (it == attributeBindings.end()) + attributeBindings.emplace_back(desc); + else + { + // All attribute sharing the same binding slot should have the same description. + ENSURE(desc.inputRate == it->inputRate && desc.stride == it->stride); + } + attributes.push_back({ + streamLocation, + vertexAttributeFormat.bindingSlot, + Mapping::FromFormat(vertexAttributeFormat.format), + vertexAttributeFormat.offset + }); + } + + VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo{}; + vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputCreateInfo.vertexBindingDescriptionCount = std::size(attributeBindings); + vertexInputCreateInfo.pVertexBindingDescriptions = attributeBindings.data(); + vertexInputCreateInfo.vertexAttributeDescriptionCount = std::size(attributes); + vertexInputCreateInfo.pVertexAttributeDescriptions = attributes.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo{}; + inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE; + + // We don't need to specify sizes for viewports and scissors as they're in + // dynamic state. + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = 0.0f; + viewport.height = 0.0f; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + + VkPipelineViewportStateCreateInfo viewportStateCreateInfo{}; + viewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportStateCreateInfo.viewportCount = 1; + viewportStateCreateInfo.pViewports = &viewport; + viewportStateCreateInfo.scissorCount = 1; + viewportStateCreateInfo.pScissors = &scissor; + + VkPipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo{}; + depthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilStateCreateInfo.depthTestEnable = + m_Desc.depthStencilState.depthTestEnabled ? VK_TRUE : VK_FALSE; + depthStencilStateCreateInfo.depthWriteEnable = + m_Desc.depthStencilState.depthWriteEnabled ? VK_TRUE : VK_FALSE; + depthStencilStateCreateInfo.depthCompareOp = + Mapping::FromCompareOp(m_Desc.depthStencilState.depthCompareOp); + depthStencilStateCreateInfo.stencilTestEnable = + m_Desc.depthStencilState.stencilTestEnabled ? VK_TRUE : VK_FALSE; + // TODO: VkStencilOpState front, back. + + VkPipelineRasterizationStateCreateInfo rasterizationStateCreateInfo{}; + rasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizationStateCreateInfo.depthClampEnable = VK_FALSE; + rasterizationStateCreateInfo.rasterizerDiscardEnable = VK_FALSE; + + rasterizationStateCreateInfo.polygonMode = + Mapping::FromPolygonMode(m_Desc.rasterizationState.polygonMode); + rasterizationStateCreateInfo.cullMode = + Mapping::FromCullMode(m_Desc.rasterizationState.cullMode); + rasterizationStateCreateInfo.frontFace = + m_Desc.rasterizationState.frontFace == FrontFace::CLOCKWISE + ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; + + rasterizationStateCreateInfo.depthBiasEnable = + m_Desc.rasterizationState.depthBiasEnabled ? VK_TRUE : VK_FALSE; + rasterizationStateCreateInfo.depthBiasConstantFactor = + m_Desc.rasterizationState.depthBiasConstantFactor; + rasterizationStateCreateInfo.depthBiasSlopeFactor = + m_Desc.rasterizationState.depthBiasSlopeFactor; + rasterizationStateCreateInfo.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampleStateCreateInfo{}; + multisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampleStateCreateInfo.rasterizationSamples = + Mapping::FromSampleCount(framebuffer->GetSampleCount()); + multisampleStateCreateInfo.minSampleShading = 1.0f; + + VkPipelineColorBlendAttachmentState colorBlendAttachmentState{}; + colorBlendAttachmentState.blendEnable = m_Desc.blendState.enabled ? VK_TRUE : VK_FALSE; + colorBlendAttachmentState.colorBlendOp = + Mapping::FromBlendOp(m_Desc.blendState.colorBlendOp); + colorBlendAttachmentState.srcColorBlendFactor = + Mapping::FromBlendFactor(m_Desc.blendState.srcColorBlendFactor); + colorBlendAttachmentState.dstColorBlendFactor = + Mapping::FromBlendFactor(m_Desc.blendState.dstColorBlendFactor); + colorBlendAttachmentState.alphaBlendOp = + Mapping::FromBlendOp(m_Desc.blendState.alphaBlendOp); + colorBlendAttachmentState.srcAlphaBlendFactor = + Mapping::FromBlendFactor(m_Desc.blendState.srcAlphaBlendFactor); + colorBlendAttachmentState.dstAlphaBlendFactor = + Mapping::FromBlendFactor(m_Desc.blendState.dstAlphaBlendFactor); + colorBlendAttachmentState.colorWriteMask = + Mapping::FromColorWriteMask(m_Desc.blendState.colorWriteMask); + + VkPipelineColorBlendStateCreateInfo colorBlendStateCreateInfo{}; + colorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlendStateCreateInfo.logicOpEnable = VK_FALSE; + colorBlendStateCreateInfo.logicOp = VK_LOGIC_OP_CLEAR; + colorBlendStateCreateInfo.attachmentCount = 1; + colorBlendStateCreateInfo.pAttachments = &colorBlendAttachmentState; + colorBlendStateCreateInfo.blendConstants[0] = m_Desc.blendState.constant.r; + colorBlendStateCreateInfo.blendConstants[1] = m_Desc.blendState.constant.g; + colorBlendStateCreateInfo.blendConstants[2] = m_Desc.blendState.constant.b; + colorBlendStateCreateInfo.blendConstants[3] = m_Desc.blendState.constant.a; + + const VkDynamicState dynamicStates[] = + { + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_VIEWPORT + }; + + VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo{}; + dynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicStateCreateInfo.dynamicStateCount = static_cast(std::size(dynamicStates)); + dynamicStateCreateInfo.pDynamicStates = dynamicStates; + + VkGraphicsPipelineCreateInfo pipelineCreateInfo{}; + pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + + pipelineCreateInfo.stageCount = shaderProgram->GetStages().size(); + pipelineCreateInfo.pStages = shaderProgram->GetStages().data(); + + pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; + pipelineCreateInfo.pViewportState = &viewportStateCreateInfo; + pipelineCreateInfo.pRasterizationState = &rasterizationStateCreateInfo; + pipelineCreateInfo.pMultisampleState = &multisampleStateCreateInfo; + // If renderPass is not VK_NULL_HANDLE, the pipeline is being created with + // fragment shader state, and subpass uses a depth/stencil attachment, + // pDepthStencilState must be a not null pointer. + if (framebuffer->GetDepthStencilAttachment()) + pipelineCreateInfo.pDepthStencilState = &depthStencilStateCreateInfo; + pipelineCreateInfo.pColorBlendState = &colorBlendStateCreateInfo; + pipelineCreateInfo.pDynamicState = &dynamicStateCreateInfo; + + pipelineCreateInfo.layout = shaderProgram->GetPipelineLayout(); + pipelineCreateInfo.renderPass = framebuffer->GetRenderPass(); + pipelineCreateInfo.subpass = 0; + pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineCreateInfo.basePipelineIndex = -1; + + VkPipeline pipeline = VK_NULL_HANDLE; + ENSURE_VK_SUCCESS(vkCreateGraphicsPipelines( + m_Device->GetVkDevice(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline)); + + m_PipelineMap[cacheKey] = pipeline; + + return pipeline; +} + +IDevice* CGraphicsPipelineState::GetDevice() +{ + return m_Device; +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/RenderPassManager.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/RenderPassManager.h +++ ps/trunk/source/renderer/backend/vulkan/RenderPassManager.h @@ -0,0 +1,89 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_RENDERPASSMANAGER +#define INCLUDED_RENDERER_BACKEND_VULKAN_RENDERPASSMANAGER + +#include "renderer/backend/IFramebuffer.h" + +#include +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; + +/** + * A helper class to store unique render passes. + */ +class CRenderPassManager +{ +public: + CRenderPassManager(CDevice* device); + ~CRenderPassManager(); + + /** + * @return a renderpass with required attachments. Currently we use only + * single subpass renderpasses. + * @note it should be called as rarely as possible. + */ + VkRenderPass GetOrCreateRenderPass( + SColorAttachment* colorAttachment, + SDepthStencilAttachment* depthStencilAttachment); + +private: + CDevice* m_Device = nullptr; + + struct Attachment + { + VkFormat format; + AttachmentLoadOp loadOp; + AttachmentStoreOp storeOp; + }; + struct Desc + { + uint8_t sampleCount; + std::optional colorAttachment; + std::optional depthStencilAttachment; + }; + struct DescHash + { + size_t operator()(const Desc& desc) const; + }; + struct DescEqual + { + bool operator()(const Desc& lhs, const Desc& rhs) const; + }; + std::unordered_map m_RenderPassMap; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_RENDERPASSMANAGER Index: ps/trunk/source/renderer/backend/vulkan/RenderPassManager.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/RenderPassManager.cpp +++ ps/trunk/source/renderer/backend/vulkan/RenderPassManager.cpp @@ -0,0 +1,200 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "RenderPassManager.h" + +#include "lib/hash.h" +#include "ps/containers/StaticVector.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/Utilities.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +size_t CRenderPassManager::DescHash::operator()(const Desc& desc) const +{ + size_t seed = 0; + hash_combine(seed, desc.sampleCount); + + if (desc.colorAttachment.has_value()) + { + hash_combine(seed, (*desc.colorAttachment).format); + hash_combine(seed, (*desc.colorAttachment).loadOp); + hash_combine(seed, (*desc.colorAttachment).storeOp); + } + else + hash_combine(seed, VK_FORMAT_UNDEFINED); + + if (desc.depthStencilAttachment.has_value()) + { + hash_combine(seed, (*desc.depthStencilAttachment).format); + hash_combine(seed, (*desc.depthStencilAttachment).loadOp); + hash_combine(seed, (*desc.depthStencilAttachment).storeOp); + } + else + hash_combine(seed, VK_FORMAT_UNDEFINED); + + return seed; +} + +bool CRenderPassManager::DescEqual::operator()(const Desc& lhs, const Desc& rhs) const +{ + auto compareAttachments = [](const std::optional& lhs, const std::optional& rhs) -> bool + { + if (lhs.has_value() != rhs.has_value()) + return false; + if (!lhs.has_value()) + return true; + return + (*lhs).format == (*rhs).format && + (*lhs).loadOp == (*rhs).loadOp && + (*lhs).storeOp == (*rhs).storeOp; + }; + if (!compareAttachments(lhs.colorAttachment, rhs.colorAttachment)) + return false; + if (!compareAttachments(lhs.depthStencilAttachment, rhs.depthStencilAttachment)) + return false; + return lhs.sampleCount == rhs.sampleCount; +} + +CRenderPassManager::CRenderPassManager(CDevice* device) + : m_Device(device) +{ +} + +CRenderPassManager::~CRenderPassManager() +{ + for (const auto& it : m_RenderPassMap) + if (it.second != VK_NULL_HANDLE) + { + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_RENDER_PASS, it.second, VK_NULL_HANDLE); + } + m_RenderPassMap.clear(); +} + +VkRenderPass CRenderPassManager::GetOrCreateRenderPass( + SColorAttachment* colorAttachment, + SDepthStencilAttachment* depthStencilAttachment) +{ + Desc desc{}; + if (colorAttachment) + { + CTexture* colorAttachmentTexture = colorAttachment->texture->As(); + desc.sampleCount = colorAttachmentTexture->GetSampleCount(); + desc.colorAttachment.emplace(Attachment{ + colorAttachmentTexture->GetVkFormat(), + colorAttachment->loadOp, + colorAttachment->storeOp}); + } + if (depthStencilAttachment) + { + CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As(); + desc.sampleCount = depthStencilAttachmentTexture->GetSampleCount(); + desc.depthStencilAttachment.emplace(Attachment{ + depthStencilAttachmentTexture->GetVkFormat(), + depthStencilAttachment->loadOp, + depthStencilAttachment->storeOp}); + } + auto it = m_RenderPassMap.find(desc); + if (it != m_RenderPassMap.end()) + return it->second; + + uint32_t attachmentCount = 0; + PS::StaticVector attachmentDescs; + std::optional colorAttachmentRef; + std::optional depthStencilAttachmentRef; + + if (colorAttachment) + { + CTexture* colorAttachmentTexture = colorAttachment->texture->As(); + + VkAttachmentDescription colorAttachmentDesc{}; + colorAttachmentDesc.format = colorAttachmentTexture->GetVkFormat(); + colorAttachmentDesc.samples = Mapping::FromSampleCount(colorAttachmentTexture->GetSampleCount()); + colorAttachmentDesc.loadOp = Mapping::FromAttachmentLoadOp(colorAttachment->loadOp); + colorAttachmentDesc.storeOp = Mapping::FromAttachmentStoreOp(colorAttachment->storeOp); + colorAttachmentDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachmentDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + attachmentDescs.emplace_back(std::move(colorAttachmentDesc)); + + colorAttachmentRef.emplace(VkAttachmentReference{ + attachmentCount++, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); + } + + if (depthStencilAttachment) + { + CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As(); + + VkAttachmentDescription depthStencilAttachmentDesc{}; + depthStencilAttachmentDesc.format = depthStencilAttachmentTexture->GetVkFormat(); + depthStencilAttachmentDesc.samples = Mapping::FromSampleCount(depthStencilAttachmentTexture->GetSampleCount()); + depthStencilAttachmentDesc.loadOp = Mapping::FromAttachmentLoadOp(depthStencilAttachment->loadOp); + depthStencilAttachmentDesc.storeOp = Mapping::FromAttachmentStoreOp(depthStencilAttachment->storeOp); + depthStencilAttachmentDesc.stencilLoadOp = depthStencilAttachmentDesc.loadOp; + depthStencilAttachmentDesc.stencilStoreOp = depthStencilAttachmentDesc.storeOp; + depthStencilAttachmentDesc.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthStencilAttachmentDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + attachmentDescs.emplace_back(std::move(depthStencilAttachmentDesc)); + + depthStencilAttachmentRef.emplace(VkAttachmentReference{ + attachmentCount++, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}); + } + + VkSubpassDescription subpassDesc{}; + subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + if (colorAttachment) + { + subpassDesc.colorAttachmentCount = 1; + subpassDesc.pColorAttachments = &(*colorAttachmentRef); + } + if (depthStencilAttachment) + subpassDesc.pDepthStencilAttachment = &(*depthStencilAttachmentRef); + + VkRenderPassCreateInfo renderPassCreateInfo{}; + renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCreateInfo.attachmentCount = attachmentDescs.size(); + renderPassCreateInfo.pAttachments = attachmentDescs.data(); + renderPassCreateInfo.subpassCount = 1; + renderPassCreateInfo.pSubpasses = &subpassDesc; + + VkRenderPass renderPass = VK_NULL_HANDLE; + ENSURE_VK_SUCCESS( + vkCreateRenderPass( + m_Device->GetVkDevice(), &renderPassCreateInfo, nullptr, &renderPass)); + it = m_RenderPassMap.emplace(desc, renderPass).first; + + return renderPass; +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/RingCommandContext.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/RingCommandContext.h +++ ps/trunk/source/renderer/backend/vulkan/RingCommandContext.h @@ -0,0 +1,132 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_RINGCOMMANDCONTEXT +#define INCLUDED_RENDERER_BACKEND_VULKAN_RINGCOMMANDCONTEXT + +#include "renderer/backend/vulkan/SubmitScheduler.h" + +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CBuffer; +class CDevice; + +/** + * A simple helper class to decouple command buffers rotation from frames + * presenting. It might be useful when sometimes we need to submit more work + * than we can usually have during a frame. For example if we need to upload + * something, an upload buffer is full and we can't extend it at the moment. + * Then the only way is to wait until uploading is done and submit more work. + * @note not thread-safe, should be created and used from the same thread. + */ +class CRingCommandContext +{ +public: + CRingCommandContext( + CDevice* device, const size_t size, const uint32_t queueFamilyIndex, + CSubmitScheduler& submitScheduler); + ~CRingCommandContext(); + + /** + * @return the current available command buffer. If there is none waits until + * it appeared. + */ + VkCommandBuffer GetCommandBuffer(); + + /** + * Submits the current command buffer to the SubmitScheduler. + */ + void Flush(); + + /** + * Schedules uploads until next render pass or flush. + * @note doesn't save a command buffer returned by GetCommandBuffer during + * scheduling uploads, because it might be changed. + */ + void ScheduleUpload( + CTexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t level, const uint32_t layer); + void ScheduleUpload( + CTexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t xOffset, const uint32_t yOffset, + const uint32_t width, const uint32_t height, + const uint32_t level, const uint32_t layer); + + void ScheduleUpload( + CBuffer* buffer, const void* data, const uint32_t dataOffset, + const uint32_t dataSize); + using UploadBufferFunction = std::function; + void ScheduleUpload( + CBuffer* buffer, + const uint32_t dataOffset, const uint32_t dataSize, + const UploadBufferFunction& uploadFunction); + +private: + void Begin(); + void End(); + + void ScheduleUpload( + CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, + const uint32_t acquiredOffset); + + uint32_t AcquireFreeSpace( + const uint32_t requiredSize, const uint32_t requiredAlignment); + uint32_t GetFreeSpaceOffset( + const uint32_t requiredSize, const uint32_t requiredAlignment) const; + + CDevice* m_Device = nullptr; + CSubmitScheduler& m_SubmitScheduler; + + std::unique_ptr m_StagingBuffer; + uint32_t m_StagingBufferFirst = 0, m_StagingBufferCurrentFirst = 0, m_StagingBufferLast = 0; + uint32_t m_OptimalBufferCopyOffsetAlignment = 1; + uint32_t m_MaxStagingBufferCapacity = 0; + + struct RingItem + { + VkCommandPool commandPool = VK_NULL_HANDLE; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + CSubmitScheduler::SubmitHandle handle = CSubmitScheduler::INVALID_SUBMIT_HANDLE; + bool isBegan = false; + uint32_t stagingBufferFirst = 0, stagingBufferLast = 0; + }; + std::vector m_Ring; + size_t m_RingIndex = 0; + + void WaitUntilFree(RingItem& item); +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_RINGCOMMANDCONTEXT Index: ps/trunk/source/renderer/backend/vulkan/RingCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/RingCommandContext.cpp +++ ps/trunk/source/renderer/backend/vulkan/RingCommandContext.cpp @@ -0,0 +1,431 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "RingCommandContext.h" + +#include "lib/bits.h" +#include "renderer/backend/vulkan/Buffer.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Utilities.h" + +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace +{ + +constexpr uint32_t INITIAL_STAGING_BUFFER_CAPACITY = 1024 * 1024; +constexpr VkDeviceSize SMALL_HOST_TOTAL_MEMORY_THRESHOLD = 1024 * 1024 * 1024; +constexpr uint32_t MAX_SMALL_STAGING_BUFFER_CAPACITY = 64 * 1024 * 1024; +constexpr uint32_t MAX_STAGING_BUFFER_CAPACITY = 256 * 1024 * 1024; + +constexpr uint32_t INVALID_OFFSET = std::numeric_limits::max(); + +} // anonymous namespace + +CRingCommandContext::CRingCommandContext( + CDevice* device, const size_t size, const uint32_t queueFamilyIndex, + CSubmitScheduler& submitScheduler) + : m_Device(device), m_SubmitScheduler(submitScheduler) +{ + ENSURE(m_Device); + + m_OptimalBufferCopyOffsetAlignment = std::max( + 1u, static_cast(m_Device->GetChoosenPhysicalDevice().properties.limits.optimalBufferCopyOffsetAlignment)); + // In case of small amount of host memory it's better to make uploading + // slower rather than crashing due to OOM, because memory for a + // staging buffer is allocated in the host memory. + m_MaxStagingBufferCapacity = + m_Device->GetChoosenPhysicalDevice().hostTotalMemory <= SMALL_HOST_TOTAL_MEMORY_THRESHOLD + ? MAX_SMALL_STAGING_BUFFER_CAPACITY + : MAX_STAGING_BUFFER_CAPACITY; + + m_Ring.resize(size); + for (RingItem& item : m_Ring) + { + VkCommandPoolCreateInfo commandPoolCreateInfoInfo{}; + commandPoolCreateInfoInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + commandPoolCreateInfoInfo.queueFamilyIndex = queueFamilyIndex; + ENSURE_VK_SUCCESS(vkCreateCommandPool( + m_Device->GetVkDevice(), &commandPoolCreateInfoInfo, + nullptr, &item.commandPool)); + + VkCommandBufferAllocateInfo allocateInfo{}; + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandPool = item.commandPool; + allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocateInfo.commandBufferCount = 1; + ENSURE_VK_SUCCESS(vkAllocateCommandBuffers( + m_Device->GetVkDevice(), &allocateInfo, &item.commandBuffer)); + device->SetObjectName( + VK_OBJECT_TYPE_COMMAND_BUFFER, item.commandBuffer, "RingCommandBuffer"); + } +} + +CRingCommandContext::~CRingCommandContext() +{ + VkDevice device = m_Device->GetVkDevice(); + for (RingItem& item : m_Ring) + { + if (item.commandBuffer != VK_NULL_HANDLE) + vkFreeCommandBuffers(device, item.commandPool, 1, &item.commandBuffer); + + if (item.commandPool != VK_NULL_HANDLE) + vkDestroyCommandPool(device, item.commandPool, nullptr); + } +} + +VkCommandBuffer CRingCommandContext::GetCommandBuffer() +{ + RingItem& item = m_Ring[m_RingIndex]; + if (!item.isBegan) + Begin(); + return item.commandBuffer; +} + +void CRingCommandContext::Flush() +{ + RingItem& item = m_Ring[m_RingIndex]; + if (!item.isBegan) + return; + + End(); + + item.handle = m_SubmitScheduler.Submit(item.commandBuffer); + + m_RingIndex = (m_RingIndex + 1) % m_Ring.size(); +} + +void CRingCommandContext::ScheduleUpload( + CTexture* texture, const Format dataFormat, + const void* data, const size_t dataSize, + const uint32_t level, const uint32_t layer) +{ + const uint32_t mininumSize = 1u; + const uint32_t width = std::max(mininumSize, texture->GetWidth() >> level); + const uint32_t height = std::max(mininumSize, texture->GetHeight() >> level); + ScheduleUpload( + texture, dataFormat, data, dataSize, + 0, 0, width, height, level, layer); +} + +void CRingCommandContext::ScheduleUpload( + CTexture* texture, const Format UNUSED(dataFormat), + const void* data, const size_t dataSize, + const uint32_t xOffset, const uint32_t yOffset, + const uint32_t width, const uint32_t height, + const uint32_t level, const uint32_t layer) +{ + ENSURE(texture->GetType() != ITexture::Type::TEXTURE_2D_MULTISAMPLE); + const Format format = texture->GetFormat(); + if (texture->GetType() != ITexture::Type::TEXTURE_CUBE) + ENSURE(layer == 0); + ENSURE(format != Format::R8G8B8_UNORM); + + const bool isCompressedFormat = + format == Format::BC1_RGB_UNORM || + format == Format::BC1_RGBA_UNORM || + format == Format::BC2_UNORM || + format == Format::BC3_UNORM; + ENSURE( + format == Format::R8_UNORM || + format == Format::R8G8_UNORM || + format == Format::R8G8B8A8_UNORM || + format == Format::A8_UNORM || + format == Format::L8_UNORM || + isCompressedFormat); + + // TODO: use a more precise format alignment. + constexpr uint32_t formatAlignment = 16; + const uint32_t offset = AcquireFreeSpace(dataSize, std::max(formatAlignment, m_OptimalBufferCopyOffsetAlignment)); + + std::memcpy(static_cast(m_StagingBuffer->GetMappedData()) + offset, data, dataSize); + + VkCommandBuffer commandBuffer = GetCommandBuffer(); + VkImage image = texture->GetImage(); + + Utilities::SubmitImageMemoryBarrier( + commandBuffer, image, level, layer, + VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy region{}; + + region.bufferOffset = offset; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = level; + region.imageSubresource.baseArrayLayer = layer; + region.imageSubresource.layerCount = 1; + + region.imageOffset = {static_cast(xOffset), static_cast(yOffset), 0}; + region.imageExtent = {width, height, 1}; + + vkCmdCopyBufferToImage( + commandBuffer, m_StagingBuffer->GetVkBuffer(), image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + VkAccessFlags dstAccessFlags = VK_ACCESS_SHADER_READ_BIT; + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + Utilities::SubmitImageMemoryBarrier( + commandBuffer, image, level, layer, + VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessFlags, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask); + texture->SetInitialized(); +} + +void CRingCommandContext::ScheduleUpload( + CBuffer* buffer, const void* data, const uint32_t dataOffset, + const uint32_t dataSize) +{ + constexpr uint32_t alignment = 16; + const uint32_t offset = AcquireFreeSpace(dataSize, alignment); + + std::memcpy(static_cast(m_StagingBuffer->GetMappedData()) + offset, data, dataSize); + + ScheduleUpload(buffer, dataOffset, dataSize, offset); +} + +void CRingCommandContext::ScheduleUpload( + CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, + const UploadBufferFunction& uploadFunction) +{ + constexpr uint32_t alignment = 16; + const uint32_t offset = AcquireFreeSpace(dataSize, alignment); + + CBuffer* stagingBuffer = m_StagingBuffer->As(); + + uploadFunction(static_cast(stagingBuffer->GetMappedData()) + offset - dataOffset); + + ScheduleUpload(buffer, dataOffset, dataSize, offset); +} + +void CRingCommandContext::ScheduleUpload( + CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, + const uint32_t acquiredOffset) +{ + CBuffer* stagingBuffer = m_StagingBuffer->As(); + VkCommandBuffer commandBuffer = GetCommandBuffer(); + + VkBufferCopy region{}; + region.srcOffset = acquiredOffset; + region.dstOffset = dataOffset; + region.size = dataSize; + + // TODO: remove transfer mask from pipeline barrier, as we need to batch copies. + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + if (buffer->GetType() == IBuffer::Type::VERTEX || buffer->GetType() == IBuffer::Type::INDEX) + srcStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; + else if (buffer->GetType() == IBuffer::Type::UNIFORM) + srcStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + Utilities::SubmitPipelineBarrier( + commandBuffer, srcStageMask, dstStageMask); + + // TODO: currently we might overwrite data which triggers validation + // assertion about Write-After-Write hazard. + if (buffer->IsDynamic()) + { + Utilities::SubmitBufferMemoryBarrier( + commandBuffer, buffer, dataOffset, dataSize, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + } + + vkCmdCopyBuffer( + commandBuffer, stagingBuffer->GetVkBuffer(), buffer->GetVkBuffer(), 1, ®ion); + + VkAccessFlags srcAccessFlags = VK_ACCESS_TRANSFER_WRITE_BIT; + VkAccessFlags dstAccessFlags = 0; + srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstStageMask = 0; + if (buffer->GetType() == IBuffer::Type::VERTEX) + { + dstAccessFlags = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + dstStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; + } + else if (buffer->GetType() == IBuffer::Type::INDEX) + { + dstAccessFlags = VK_ACCESS_INDEX_READ_BIT; + dstStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; + } + else if (buffer->GetType() == IBuffer::Type::UNIFORM) + { + dstAccessFlags = VK_ACCESS_UNIFORM_READ_BIT; + dstStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + Utilities::SubmitBufferMemoryBarrier( + commandBuffer, buffer, dataOffset, dataSize, + srcAccessFlags, dstAccessFlags, srcStageMask, dstStageMask); +} + +void CRingCommandContext::Begin() +{ + RingItem& item = m_Ring[m_RingIndex]; + item.isBegan = true; + + WaitUntilFree(item); + + m_StagingBufferCurrentFirst = m_StagingBufferLast; + + ENSURE_VK_SUCCESS(vkResetCommandPool(m_Device->GetVkDevice(), item.commandPool, 0)); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; + beginInfo.pInheritanceInfo = nullptr; + ENSURE_VK_SUCCESS(vkBeginCommandBuffer(item.commandBuffer, &beginInfo)); +} + +void CRingCommandContext::End() +{ + RingItem& item = m_Ring[m_RingIndex]; + item.isBegan = false; + item.stagingBufferFirst = m_StagingBufferCurrentFirst; + item.stagingBufferLast = m_StagingBufferLast; + + ENSURE_VK_SUCCESS(vkEndCommandBuffer(item.commandBuffer)); +} + +void CRingCommandContext::WaitUntilFree(RingItem& item) +{ + m_SubmitScheduler.WaitUntilFree(item.handle); + if (item.stagingBufferFirst != item.stagingBufferLast) + { + m_StagingBufferFirst = item.stagingBufferLast; + item.stagingBufferFirst = 0; + item.stagingBufferLast = 0; + } +} + +uint32_t CRingCommandContext::AcquireFreeSpace( + const uint32_t requiredSize, const uint32_t requiredAlignment) +{ + ENSURE(requiredSize <= m_MaxStagingBufferCapacity); + const uint32_t offsetCandidate = + GetFreeSpaceOffset(requiredSize, requiredAlignment); + const bool needsResize = + !m_StagingBuffer || offsetCandidate == INVALID_OFFSET; + const bool canResize = + !m_StagingBuffer || m_StagingBuffer->GetSize() < m_MaxStagingBufferCapacity; + if (needsResize && canResize) + { + const uint32_t minimumRequiredCapacity = round_up_to_pow2(requiredSize); + const uint32_t newCapacity = std::min( + std::max(m_StagingBuffer ? m_StagingBuffer->GetSize() * 2 : INITIAL_STAGING_BUFFER_CAPACITY, minimumRequiredCapacity), + m_MaxStagingBufferCapacity); + m_StagingBuffer = m_Device->CreateCBuffer( + "UploadRingBuffer", IBuffer::Type::UPLOAD, newCapacity, false); + ENSURE(m_StagingBuffer); + m_StagingBufferFirst = 0; + m_StagingBufferCurrentFirst = 0; + m_StagingBufferLast = requiredSize; + + for (RingItem& item : m_Ring) + { + item.stagingBufferFirst = 0; + item.stagingBufferLast = 0; + } + + return 0; + } + else if (needsResize) + { + // In case we can't resize we need to wait until all scheduled uploads are + // completed. + for (size_t ringIndexOffset = 1; ringIndexOffset < m_Ring.size() && GetFreeSpaceOffset(requiredSize, requiredAlignment) == INVALID_OFFSET; ++ringIndexOffset) + { + const size_t ringIndex = (m_RingIndex + ringIndexOffset) % m_Ring.size(); + RingItem& item = m_Ring[ringIndex]; + WaitUntilFree(item); + } + // If we still don't have a free space it means we need to flush the + // current command buffer. + const uint32_t offset = GetFreeSpaceOffset(requiredSize, requiredAlignment); + if (offset == INVALID_OFFSET) + { + RingItem& item = m_Ring[m_RingIndex]; + if (item.isBegan) + Flush(); + WaitUntilFree(item); + m_StagingBufferFirst = 0; + m_StagingBufferCurrentFirst = 0; + m_StagingBufferLast = requiredSize; + return 0; + } + else + { + m_StagingBufferLast = offset + requiredSize; + return offset; + } + } + else + { + m_StagingBufferLast = offsetCandidate + requiredSize; + return offsetCandidate; + } +} + +uint32_t CRingCommandContext::GetFreeSpaceOffset( + const uint32_t requiredSize, const uint32_t requiredAlignment) const +{ + if (!m_StagingBuffer) + return INVALID_OFFSET; + const uint32_t candidateOffset = + round_up(m_StagingBufferLast, requiredAlignment); + const uint32_t candidateLast = candidateOffset + requiredSize; + if (m_StagingBufferFirst <= m_StagingBufferLast) + { + if (candidateLast <= m_StagingBuffer->GetSize()) + return candidateOffset; + // We intentionally use exclusive comparison to avoid distinguishing + // completely full and completely empty staging buffers. + else if (requiredSize < m_StagingBufferFirst) + return 0; // We assume the first byte is always perfectly aligned. + else + return INVALID_OFFSET; + } + else + { + if (candidateLast < m_StagingBufferFirst) + return candidateOffset; + else + return INVALID_OFFSET; + } +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/SamplerManager.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/SamplerManager.h +++ ps/trunk/source/renderer/backend/vulkan/SamplerManager.h @@ -0,0 +1,75 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SAMPLERMANAGER +#define INCLUDED_RENDERER_BACKEND_VULKAN_SAMPLERMANAGER + +#include "renderer/backend/Sampler.h" + +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; + +/** + * A helper class to store unique samplers. The manager doesn't track usages of + * its samplers but keep them alive until its end. So before destroying the + * manager its owner should guarantee no usage. + */ +class CSamplerManager +{ +public: + CSamplerManager(CDevice* device); + ~CSamplerManager(); + + /** + * @return a sampler matches the description. The returned sampler is owned by + * the manager. + */ + VkSampler GetOrCreateSampler(const Sampler::Desc& samplerDesc); + +private: + CDevice* m_Device = nullptr; + + struct SamplerDescHash + { + size_t operator()(const Sampler::Desc& samplerDesc) const; + }; + struct SamplerDescEqual + { + bool operator()(const Sampler::Desc& lhs, const Sampler::Desc& rhs) const; + }; + std::unordered_map m_SamplerMap; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SAMPLERMANAGER Index: ps/trunk/source/renderer/backend/vulkan/SamplerManager.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/SamplerManager.cpp +++ ps/trunk/source/renderer/backend/vulkan/SamplerManager.cpp @@ -0,0 +1,140 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "SamplerManager.h" + +#include "lib/hash.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/Utilities.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +size_t CSamplerManager::SamplerDescHash::operator()(const Sampler::Desc& samplerDesc) const +{ + size_t seed = 0; + + hash_combine(seed, samplerDesc.magFilter); + hash_combine(seed, samplerDesc.minFilter); + hash_combine(seed, samplerDesc.mipFilter); + hash_combine(seed, samplerDesc.addressModeU); + hash_combine(seed, samplerDesc.addressModeV); + hash_combine(seed, samplerDesc.addressModeW); + + hash_combine(seed, samplerDesc.mipLODBias); + hash_combine(seed, samplerDesc.anisotropyEnabled); + hash_combine(seed, samplerDesc.maxAnisotropy); + + hash_combine(seed, samplerDesc.borderColor); + hash_combine(seed, samplerDesc.compareEnabled); + hash_combine(seed, samplerDesc.compareOp); + + return seed; +} + +bool CSamplerManager::SamplerDescEqual::operator()(const Sampler::Desc& lhs, const Sampler::Desc& rhs) const +{ + return + lhs.magFilter == rhs.magFilter && + lhs.minFilter == rhs.minFilter && + lhs.mipFilter == rhs.mipFilter && + lhs.addressModeU == rhs.addressModeU && + lhs.addressModeV == rhs.addressModeV && + lhs.addressModeW == rhs.addressModeW && + lhs.mipLODBias == rhs.mipLODBias && + lhs.anisotropyEnabled == rhs.anisotropyEnabled && + lhs.maxAnisotropy == rhs.maxAnisotropy && + lhs.borderColor == rhs.borderColor && + lhs.compareEnabled == rhs.compareEnabled && + lhs.compareOp == rhs.compareOp; +} + +CSamplerManager::CSamplerManager(CDevice* device) + : m_Device(device) +{ +} + +CSamplerManager::~CSamplerManager() +{ + for (const auto& it : m_SamplerMap) + if (it.second != VK_NULL_HANDLE) + { + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_SAMPLER, it.second, VK_NULL_HANDLE); + } + m_SamplerMap.clear(); +} + +VkSampler CSamplerManager::GetOrCreateSampler( + const Sampler::Desc& samplerDesc) +{ + auto it = m_SamplerMap.find(samplerDesc); + if (it != m_SamplerMap.end()) + return it->second; + + VkSamplerCreateInfo samplerCreateInfo{}; + samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerCreateInfo.magFilter = samplerDesc.magFilter == Sampler::Filter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + samplerCreateInfo.minFilter = samplerDesc.minFilter == Sampler::Filter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + samplerCreateInfo.mipmapMode = samplerDesc.mipFilter == Sampler::Filter::LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerCreateInfo.addressModeU = Mapping::FromAddressMode(samplerDesc.addressModeU); + samplerCreateInfo.addressModeV = Mapping::FromAddressMode(samplerDesc.addressModeV); + samplerCreateInfo.addressModeW = Mapping::FromAddressMode(samplerDesc.addressModeW); + if (samplerDesc.anisotropyEnabled && m_Device->GetCapabilities().anisotropicFiltering) + { + samplerCreateInfo.anisotropyEnable = VK_TRUE; + samplerCreateInfo.maxAnisotropy = samplerDesc.maxAnisotropy; + } + samplerCreateInfo.compareEnable = samplerDesc.compareEnabled ? VK_TRUE : VK_FALSE; + samplerCreateInfo.compareOp = Mapping::FromCompareOp(samplerDesc.compareOp); + samplerCreateInfo.minLod = -1000.0f; + samplerCreateInfo.maxLod = 1000.0f; + switch (samplerDesc.borderColor) + { + case Sampler::BorderColor::TRANSPARENT_BLACK: + samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + break; + case Sampler::BorderColor::OPAQUE_BLACK: + samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + break; + case Sampler::BorderColor::OPAQUE_WHITE: + samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + break; + } + + VkSampler sampler = VK_NULL_HANDLE; + ENSURE_VK_SUCCESS(vkCreateSampler( + m_Device->GetVkDevice(), &samplerCreateInfo, nullptr, &sampler)); + it = m_SamplerMap.emplace(samplerDesc, sampler).first; + + return sampler; +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/ShaderProgram.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/ShaderProgram.h +++ ps/trunk/source/renderer/backend/vulkan/ShaderProgram.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM +#define INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM + +#include "renderer/backend/IShaderProgram.h" +#include "renderer/backend/vulkan/Texture.h" + +#include +#include +#include +#include +#include +#include + +class CShaderDefines; +class CStr; + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; + +class CVertexInputLayout : public IVertexInputLayout +{ +public: + CVertexInputLayout(CDevice* device, const PS::span attributes) + : m_Device(device), m_Attributes(attributes.begin(), attributes.end()) + { + static uint32_t m_LastAvailableUID = 1; + m_UID = m_LastAvailableUID++; + for (const SVertexAttributeFormat& attribute : m_Attributes) + { + ENSURE(attribute.format != Format::UNDEFINED); + ENSURE(attribute.stride > 0); + } + } + + ~CVertexInputLayout() override = default; + + IDevice* GetDevice() override; + + const std::vector& GetAttributes() const noexcept { return m_Attributes; } + + using UID = uint32_t; + UID GetUID() const { return m_UID; } + +private: + CDevice* m_Device = nullptr; + + UID m_UID = 0; + + std::vector m_Attributes; +}; + +class CShaderProgram final : public IShaderProgram +{ +public: + ~CShaderProgram() override; + + IDevice* GetDevice() override; + + int32_t GetBindingSlot(const CStrIntern name) const override; + + std::vector GetFileDependencies() const override; + + uint32_t GetStreamLocation(const VertexAttributeStream stream) const; + + const std::vector& GetStages() const { return m_Stages; } + + void Bind(); + void Unbind(); + void PreDraw(VkCommandBuffer commandBuffer); + + VkPipelineLayout GetPipelineLayout() const { return m_PipelineLayout; } + VkPipelineBindPoint GetPipelineBindPoint() const { return VK_PIPELINE_BIND_POINT_GRAPHICS; } + + void SetUniform( + const int32_t bindingSlot, + const float value); + void SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY); + void SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ); + void SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ, const float valueW); + void SetUniform( + const int32_t bindingSlot, PS::span values); + void SetTexture(const int32_t bindingSlot, CTexture* texture); + + // TODO: rename to something related to buffer. + bool IsMaterialConstantsDataOutdated() const { return m_MaterialConstantsDataOutdated; } + void UpdateMaterialConstantsData() { m_MaterialConstantsDataOutdated = false; } + std::byte* GetMaterialConstantsData() const { return m_MaterialConstantsData.get(); } + uint32_t GetMaterialConstantsDataSize() const { return m_MaterialConstantsDataSize; } + +private: + friend class CDevice; + + CShaderProgram(); + + std::pair GetUniformData( + const int32_t bindingSlot, const uint32_t dataSize); + + static std::unique_ptr Create( + CDevice* device, const CStr& name, const CShaderDefines& defines); + + void UpdateActiveDescriptorSet( + VkCommandBuffer commandBuffer); + + CDevice* m_Device = nullptr; + + std::vector m_ShaderModules; + std::vector m_Stages; + VkPipelineLayout m_PipelineLayout = VK_NULL_HANDLE; + + std::vector m_FileDependencies; + + struct PushConstant + { + CStrIntern name; + uint32_t offset; + uint32_t size; + VkShaderStageFlags stageFlags; + }; + struct Uniform + { + CStrIntern name; + uint32_t offset; + uint32_t size; + }; + std::unique_ptr m_MaterialConstantsData; + uint32_t m_MaterialConstantsDataSize = 0; + bool m_MaterialConstantsDataOutdated = false; + std::array m_PushConstantData; + uint32_t m_PushConstantDataMask = 0; + std::array m_PushConstantDataFlags; + std::vector m_PushConstants; + std::vector m_Uniforms; + std::unordered_map m_UniformMapping; + std::unordered_map m_PushConstantMapping; + + uint32_t m_TexturesDescriptorSetSize = 0; + bool m_BoundTexturesOutdated = false; + + VkDescriptorSetLayout m_TexturesDescriptorSetLayout = VK_NULL_HANDLE; + std::vector m_BoundTextures; + std::vector m_BoundTexturesUID; + VkDescriptorSet m_ActiveTexturesDescriptorSet = VK_NULL_HANDLE; + std::unordered_map m_TextureMapping; + + std::unordered_map m_StreamLocations; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM Index: ps/trunk/source/renderer/backend/vulkan/ShaderProgram.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/ShaderProgram.cpp +++ ps/trunk/source/renderer/backend/vulkan/ShaderProgram.cpp @@ -0,0 +1,699 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ShaderProgram.h" + +#include "graphics/ShaderDefines.h" +#include "ps/CLogger.h" +#include "ps/CStr.h" +#include "ps/CStrInternStatic.h" +#include "ps/Filesystem.h" +#include "ps/Profile.h" +#include "ps/XML/Xeromyces.h" +#include "renderer/backend/vulkan/DescriptorManager.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Texture.h" + +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace +{ + +VkShaderModule CreateShaderModule(CDevice* device, const VfsPath& path) +{ + CVFSFile file; + if (file.Load(g_VFS, path) != PSRETURN_OK) + { + LOGERROR("Failed to load shader file: '%s'", path.string8()); + return VK_NULL_HANDLE; + } + + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + // Casting to uint32_t requires to fit alignment and size. + ENSURE(file.GetBufferSize() % 4 == 0); + ENSURE(reinterpret_cast(file.GetBuffer()) % alignof(uint32_t) == 0u); + createInfo.codeSize = file.GetBufferSize(); + createInfo.pCode = reinterpret_cast(file.GetBuffer()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device->GetVkDevice(), &createInfo, nullptr, &shaderModule) != VK_SUCCESS) + { + LOGERROR("Failed to create shader module from file: '%s'", path.string8()); + return VK_NULL_HANDLE; + } + device->SetObjectName(VK_OBJECT_TYPE_SHADER_MODULE, shaderModule, path.string8().c_str()); + return shaderModule; +} + +VfsPath FindProgramMatchingDefines(const VfsPath& xmlFilename, const CShaderDefines& defines) +{ + CXeromyces xeroFile; + PSRETURN ret = xeroFile.Load(g_VFS, xmlFilename); + if (ret != PSRETURN_OK) + return {}; + + // TODO: add XML validation. + +#define EL(x) const int el_##x = xeroFile.GetElementID(#x) +#define AT(x) const int at_##x = xeroFile.GetAttributeID(#x) + EL(define); + EL(defines); + EL(program); + AT(file); + AT(name); + AT(value); +#undef AT +#undef EL + + const CStrIntern strUndefined("UNDEFINED"); + VfsPath programFilename; + XMBElement root = xeroFile.GetRoot(); + XERO_ITER_EL(root, rootChild) + { + if (rootChild.GetNodeName() == el_program) + { + CShaderDefines programDefines; + XERO_ITER_EL(rootChild, programChild) + { + if (programChild.GetNodeName() == el_defines) + { + XERO_ITER_EL(programChild, definesChild) + { + XMBAttributeList attributes = definesChild.GetAttributes(); + if (definesChild.GetNodeName() == el_define) + { + const CStrIntern value(attributes.GetNamedItem(at_value)); + if (value == strUndefined) + continue; + programDefines.Add( + CStrIntern(attributes.GetNamedItem(at_name)), value); + } + } + } + } + + if (programDefines == defines) + return L"shaders/" + rootChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); + } + } + + return {}; +} + +} // anonymous namespace + +IDevice* CVertexInputLayout::GetDevice() +{ + return m_Device; +} + +// static +std::unique_ptr CShaderProgram::Create( + CDevice* device, const CStr& name, const CShaderDefines& baseDefines) +{ + const VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; + + std::unique_ptr shaderProgram(new CShaderProgram()); + shaderProgram->m_Device = device; + shaderProgram->m_FileDependencies = {xmlFilename}; + + CShaderDefines defines = baseDefines; + if (device->GetDescriptorManager().UseDescriptorIndexing()) + defines.Add(str_USE_DESCRIPTOR_INDEXING, str_1); + + const VfsPath programFilename = FindProgramMatchingDefines(xmlFilename, defines); + if (programFilename.empty()) + { + LOGERROR("Program '%s' with required defines not found.", name); + for (const auto& pair : defines.GetMap()) + LOGERROR(" \"%s\": \"%s\"", pair.first.c_str(), pair.second.c_str()); + return nullptr; + } + shaderProgram->m_FileDependencies.emplace_back(programFilename); + + CXeromyces programXeroFile; + if (programXeroFile.Load(g_VFS, programFilename) != PSRETURN_OK) + return nullptr; + XMBElement programRoot = programXeroFile.GetRoot(); + +#define EL(x) const int el_##x = programXeroFile.GetElementID(#x) +#define AT(x) const int at_##x = programXeroFile.GetAttributeID(#x) + EL(binding); + EL(descriptor_set); + EL(descriptor_sets); + EL(fragment); + EL(member); + EL(push_constant); + EL(stream); + EL(vertex); + AT(binding); + AT(file); + AT(location); + AT(name); + AT(offset); + AT(set); + AT(size); + AT(type); +#undef AT +#undef EL + + auto addPushConstant = + [&pushConstants=shaderProgram->m_PushConstants, &pushConstantDataFlags=shaderProgram->m_PushConstantDataFlags, &at_name, &at_offset, &at_size]( + const XMBElement& element, VkShaderStageFlags stageFlags) -> bool + { + const XMBAttributeList attributes = element.GetAttributes(); + const CStrIntern name = CStrIntern(attributes.GetNamedItem(at_name)); + const uint32_t size = attributes.GetNamedItem(at_size).ToUInt(); + const uint32_t offset = attributes.GetNamedItem(at_offset).ToUInt(); + if (offset % 4 != 0 || size % 4 != 0) + { + LOGERROR("Push constant should have offset and size be multiple of 4."); + return false; + } + for (PushConstant& pushConstant : pushConstants) + { + if (pushConstant.name == name) + { + if (size != pushConstant.size || offset != pushConstant.offset) + { + LOGERROR("All shared push constants must have the same size and offset."); + return false; + } + // We found the same constant so we don't need to add it again. + pushConstant.stageFlags |= stageFlags; + for (uint32_t index = 0; index < (size >> 2); ++index) + pushConstantDataFlags[(offset >> 2) + index] |= stageFlags; + return true; + } + if (offset + size < pushConstant.offset || offset >= pushConstant.offset + pushConstant.size) + continue; + LOGERROR("All push constant must not intersect each other in memory."); + return false; + } + pushConstants.push_back({name, offset, size, stageFlags}); + for (uint32_t index = 0; index < (size >> 2); ++index) + pushConstantDataFlags[(offset >> 2) + index] = stageFlags; + return true; + }; + + auto addDescriptorSets = [&](const XMBElement& element) -> bool + { + const bool useDescriptorIndexing = + device->GetDescriptorManager().UseDescriptorIndexing(); + // TODO: reduce the indentation. + XERO_ITER_EL(element, descriporSetsChild) + { + if (descriporSetsChild.GetNodeName() == el_descriptor_set) + { + const uint32_t set = descriporSetsChild.GetAttributes().GetNamedItem(at_set).ToUInt(); + if (useDescriptorIndexing && set == 0 && !descriporSetsChild.GetChildNodes().empty()) + { + LOGERROR("Descritor set for descriptor indexing shouldn't contain bindings."); + return false; + } + XERO_ITER_EL(descriporSetsChild, descriporSetChild) + { + if (descriporSetChild.GetNodeName() == el_binding) + { + const XMBAttributeList attributes = descriporSetChild.GetAttributes(); + const uint32_t binding = attributes.GetNamedItem(at_binding).ToUInt(); + const uint32_t size = attributes.GetNamedItem(at_size).ToUInt(); + const CStr type = attributes.GetNamedItem(at_type); + if (type == "uniform") + { + const uint32_t expectedSet = + device->GetDescriptorManager().GetUniformSet(); + if (set != expectedSet || binding != 0) + { + LOGERROR("We support only a single uniform block per shader program."); + return false; + } + shaderProgram->m_MaterialConstantsDataSize = size; + XERO_ITER_EL(descriporSetChild, bindingChild) + { + if (bindingChild.GetNodeName() == el_member) + { + const XMBAttributeList memberAttributes = bindingChild.GetAttributes(); + const uint32_t offset = memberAttributes.GetNamedItem(at_offset).ToUInt(); + const uint32_t size = memberAttributes.GetNamedItem(at_size).ToUInt(); + const CStrIntern name{memberAttributes.GetNamedItem(at_name)}; + bool found = false; + for (const Uniform& uniform : shaderProgram->m_Uniforms) + { + if (uniform.name == name) + { + if (offset != uniform.offset || size != uniform.size) + { + LOGERROR("All uniforms across all stage should match."); + return false; + } + found = true; + } + else + { + if (offset + size <= uniform.offset || uniform.offset + uniform.size <= offset) + continue; + LOGERROR("Uniforms must not overlap each other."); + return false; + } + } + if (!found) + shaderProgram->m_Uniforms.push_back({name, offset, size}); + } + } + } + else if (type == "sampler1D" || type == "sampler2D" || type == "sampler2DShadow" || type == "sampler3D" || type == "samplerCube") + { + if (useDescriptorIndexing) + { + LOGERROR("We support only uniform descriptor sets with enabled descriptor indexing."); + return false; + } + const CStrIntern name{attributes.GetNamedItem(at_name)}; + shaderProgram->m_TextureMapping[name] = binding; + shaderProgram->m_TexturesDescriptorSetSize = + std::max(shaderProgram->m_TexturesDescriptorSetSize, binding + 1); + } + else + { + LOGERROR("Unsupported binding: '%s'", type.c_str()); + return false; + } + } + } + } + } + return true; + }; + + XERO_ITER_EL(programRoot, programChild) + { + if (programChild.GetNodeName() == el_vertex) + { + const VfsPath shaderModulePath = + L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); + shaderProgram->m_FileDependencies.emplace_back(shaderModulePath); + shaderProgram->m_ShaderModules.emplace_back( + CreateShaderModule(device, shaderModulePath)); + if (shaderProgram->m_ShaderModules.back() == VK_NULL_HANDLE) + return nullptr; + VkPipelineShaderStageCreateInfo vertexShaderStageInfo{}; + vertexShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertexShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertexShaderStageInfo.module = shaderProgram->m_ShaderModules.back(); + vertexShaderStageInfo.pName = "main"; + shaderProgram->m_Stages.emplace_back(std::move(vertexShaderStageInfo)); + XERO_ITER_EL(programChild, stageChild) + { + if (stageChild.GetNodeName() == el_stream) + { + XMBAttributeList attributes = stageChild.GetAttributes(); + const uint32_t location = attributes.GetNamedItem(at_location).ToUInt(); + const CStr streamName = attributes.GetNamedItem(at_name); + VertexAttributeStream stream = VertexAttributeStream::UV7; + if (streamName == "pos") + stream = VertexAttributeStream::POSITION; + else if (streamName == "normal") + stream = VertexAttributeStream::NORMAL; + else if (streamName == "color") + stream = VertexAttributeStream::COLOR; + else if (streamName == "uv0") + stream = VertexAttributeStream::UV0; + else if (streamName == "uv1") + stream = VertexAttributeStream::UV1; + else if (streamName == "uv2") + stream = VertexAttributeStream::UV2; + else if (streamName == "uv3") + stream = VertexAttributeStream::UV3; + else if (streamName == "uv4") + stream = VertexAttributeStream::UV4; + else if (streamName == "uv5") + stream = VertexAttributeStream::UV5; + else if (streamName == "uv6") + stream = VertexAttributeStream::UV6; + else if (streamName == "uv7") + stream = VertexAttributeStream::UV7; + else + debug_warn("Unknown stream"); + shaderProgram->m_StreamLocations[stream] = location; + } + else if (stageChild.GetNodeName() == el_push_constant) + { + if (!addPushConstant(stageChild, VK_SHADER_STAGE_VERTEX_BIT)) + return nullptr; + } + else if (stageChild.GetNodeName() == el_descriptor_sets) + { + if (!addDescriptorSets(stageChild)) + return nullptr; + } + } + } + else if (programChild.GetNodeName() == el_fragment) + { + const VfsPath shaderModulePath = + L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); + shaderProgram->m_FileDependencies.emplace_back(shaderModulePath); + shaderProgram->m_ShaderModules.emplace_back( + CreateShaderModule(device, shaderModulePath)); + if (shaderProgram->m_ShaderModules.back() == VK_NULL_HANDLE) + return nullptr; + VkPipelineShaderStageCreateInfo fragmentShaderStageInfo{}; + fragmentShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragmentShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragmentShaderStageInfo.module = shaderProgram->m_ShaderModules.back(); + fragmentShaderStageInfo.pName = "main"; + shaderProgram->m_Stages.emplace_back(std::move(fragmentShaderStageInfo)); + XERO_ITER_EL(programChild, stageChild) + { + if (stageChild.GetNodeName() == el_push_constant) + { + if (!addPushConstant(stageChild, VK_SHADER_STAGE_FRAGMENT_BIT)) + return nullptr; + } + else if (stageChild.GetNodeName() == el_descriptor_sets) + { + if (!addDescriptorSets(stageChild)) + return nullptr; + } + } + } + } + + if (shaderProgram->m_Stages.empty()) + { + LOGERROR("Program should contain at least one stage."); + return nullptr; + } + + for (size_t index = 0; index < shaderProgram->m_PushConstants.size(); ++index) + shaderProgram->m_PushConstantMapping[shaderProgram->m_PushConstants[index].name] = index; + std::vector pushConstantRanges; + pushConstantRanges.reserve(shaderProgram->m_PushConstants.size()); + std::transform( + shaderProgram->m_PushConstants.begin(), shaderProgram->m_PushConstants.end(), + std::back_insert_iterator(pushConstantRanges), [](const PushConstant& pushConstant) + { + return VkPushConstantRange{pushConstant.stageFlags, pushConstant.offset, pushConstant.size}; + }); + if (!pushConstantRanges.empty()) + { + std::sort(pushConstantRanges.begin(), pushConstantRanges.end(), + [](const VkPushConstantRange& lhs, const VkPushConstantRange& rhs) + { + return lhs.offset < rhs.offset; + }); + // Merge subsequent constants. + auto it = pushConstantRanges.begin(); + while (std::next(it) != pushConstantRanges.end()) + { + auto next = std::next(it); + if (it->stageFlags == next->stageFlags) + { + it->size = next->offset - it->offset + next->size; + pushConstantRanges.erase(next); + } + else + it = next; + } + for (const VkPushConstantRange& range : pushConstantRanges) + if (std::count_if(pushConstantRanges.begin(), pushConstantRanges.end(), + [stageFlags=range.stageFlags](const VkPushConstantRange& range) { return range.stageFlags & stageFlags; }) != 1) + { + LOGERROR("Any two range must not include the same stage in stageFlags."); + return nullptr; + } + } + + for (size_t index = 0; index < shaderProgram->m_Uniforms.size(); ++index) + shaderProgram->m_UniformMapping[shaderProgram->m_Uniforms[index].name] = index; + if (!shaderProgram->m_Uniforms.empty()) + { + if (shaderProgram->m_MaterialConstantsDataSize > device->GetChoosenPhysicalDevice().properties.limits.maxUniformBufferRange) + { + LOGERROR("Uniform buffer size is too big for the device."); + return nullptr; + } + shaderProgram->m_MaterialConstantsData = + std::make_unique(shaderProgram->m_MaterialConstantsDataSize); + } + + std::vector layouts = + device->GetDescriptorManager().GetDescriptorSetLayouts(); + if (shaderProgram->m_TexturesDescriptorSetSize > 0) + { + ENSURE(!device->GetDescriptorManager().UseDescriptorIndexing()); + shaderProgram->m_BoundTextures.resize(shaderProgram->m_TexturesDescriptorSetSize); + shaderProgram->m_BoundTexturesUID.resize(shaderProgram->m_TexturesDescriptorSetSize); + shaderProgram->m_BoundTexturesOutdated = true; + shaderProgram->m_TexturesDescriptorSetLayout = + device->GetDescriptorManager().GetSingleTypeDescritorSetLayout( + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, shaderProgram->m_TexturesDescriptorSetSize); + layouts.emplace_back(shaderProgram->m_TexturesDescriptorSetLayout); + } + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{}; + pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutCreateInfo.setLayoutCount = layouts.size(); + pipelineLayoutCreateInfo.pSetLayouts = layouts.data(); + pipelineLayoutCreateInfo.pushConstantRangeCount = pushConstantRanges.size(); + pipelineLayoutCreateInfo.pPushConstantRanges = pushConstantRanges.data(); + + const VkResult result = vkCreatePipelineLayout( + device->GetVkDevice(), &pipelineLayoutCreateInfo, nullptr, + &shaderProgram->m_PipelineLayout); + if (result != VK_SUCCESS) + { + LOGERROR("Failed to create a pipeline layout: %d", static_cast(result)); + return nullptr; + } + + return shaderProgram; +} + +CShaderProgram::CShaderProgram() = default; + +CShaderProgram::~CShaderProgram() +{ + if (m_PipelineLayout != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy(VK_OBJECT_TYPE_PIPELINE_LAYOUT, m_PipelineLayout, VK_NULL_HANDLE); + for (VkShaderModule shaderModule : m_ShaderModules) + if (shaderModule != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy(VK_OBJECT_TYPE_SHADER_MODULE, shaderModule, VK_NULL_HANDLE); +} + +IDevice* CShaderProgram::GetDevice() +{ + return m_Device; +} + +int32_t CShaderProgram::GetBindingSlot(const CStrIntern name) const +{ + if (auto it = m_PushConstantMapping.find(name); it != m_PushConstantMapping.end()) + return it->second; + if (auto it = m_UniformMapping.find(name); it != m_UniformMapping.end()) + return it->second + m_PushConstants.size(); + if (auto it = m_TextureMapping.find(name); it != m_TextureMapping.end()) + return it->second + m_PushConstants.size() + m_UniformMapping.size(); + return -1; +} + +std::vector CShaderProgram::GetFileDependencies() const +{ + return m_FileDependencies; +} + +uint32_t CShaderProgram::GetStreamLocation(const VertexAttributeStream stream) const +{ + auto it = m_StreamLocations.find(stream); + return it != m_StreamLocations.end() ? it->second : std::numeric_limits::max(); +} + +void CShaderProgram::Bind() +{ + if (m_MaterialConstantsData) + m_MaterialConstantsDataOutdated = true; +} + +void CShaderProgram::Unbind() +{ + if (m_TexturesDescriptorSetSize > 0) + { + for (CTexture*& texture : m_BoundTextures) + texture = nullptr; + for (CTexture::UID& uid : m_BoundTexturesUID) + uid = 0; + m_BoundTexturesOutdated = true; + } +} + +void CShaderProgram::PreDraw(VkCommandBuffer commandBuffer) +{ + UpdateActiveDescriptorSet(commandBuffer); + if (m_PushConstantDataMask) + { + for (uint32_t index = 0; index < 32;) + { + if (!(m_PushConstantDataMask & (1 << index))) + { + ++index; + continue; + } + uint32_t indexEnd = index + 1; + while (indexEnd < 32 && (m_PushConstantDataMask & (1 << indexEnd)) && m_PushConstantDataFlags[index] == m_PushConstantDataFlags[indexEnd]) + ++indexEnd; + vkCmdPushConstants( + commandBuffer, GetPipelineLayout(), + m_PushConstantDataFlags[index], + index * 4, (indexEnd - index) * 4, m_PushConstantData.data() + index * 4); + index = indexEnd; + } + m_PushConstantDataMask = 0; + } +} + +void CShaderProgram::UpdateActiveDescriptorSet( + VkCommandBuffer commandBuffer) +{ + if (m_BoundTexturesOutdated) + { + m_BoundTexturesOutdated = false; + + m_ActiveTexturesDescriptorSet = + m_Device->GetDescriptorManager().GetSingleTypeDescritorSet( + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_TexturesDescriptorSetLayout, + m_BoundTexturesUID, m_BoundTextures); + ENSURE(m_ActiveTexturesDescriptorSet != VK_NULL_HANDLE); + + vkCmdBindDescriptorSets( + commandBuffer, GetPipelineBindPoint(), GetPipelineLayout(), + 1, 1, &m_ActiveTexturesDescriptorSet, 0, nullptr); + } +} + +void CShaderProgram::SetUniform( + const int32_t bindingSlot, + const float value) +{ + const float values[1] = {value}; + SetUniform(bindingSlot, PS::span(values, values + 1)); +} + +void CShaderProgram::SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY) +{ + const float values[2] = {valueX, valueY}; + SetUniform(bindingSlot, PS::span(values, values + 2)); +} + +void CShaderProgram::SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ) +{ + const float values[3] = {valueX, valueY, valueZ}; + SetUniform(bindingSlot, PS::span(values, values + 3)); +} + +void CShaderProgram::SetUniform( + const int32_t bindingSlot, + const float valueX, const float valueY, + const float valueZ, const float valueW) +{ + const float values[4] = {valueX, valueY, valueZ, valueW}; + SetUniform(bindingSlot, PS::span(values, values + 4)); +} + +void CShaderProgram::SetUniform(const int32_t bindingSlot, PS::span values) +{ + if (bindingSlot < 0) + return; + const auto data = GetUniformData(bindingSlot, values.size() * sizeof(float)); + std::memcpy(data.first, values.data(), data.second); +} + +std::pair CShaderProgram::GetUniformData( + const int32_t bindingSlot, const uint32_t dataSize) +{ + if (bindingSlot < static_cast(m_PushConstants.size())) + { + const uint32_t size = m_PushConstants[bindingSlot].size; + const uint32_t offset = m_PushConstants[bindingSlot].offset; + ENSURE(size <= dataSize); + m_PushConstantDataMask |= ((1 << (size >> 2)) - 1) << (offset >> 2); + return {m_PushConstantData.data() + offset, size}; + } + else + { + ENSURE(bindingSlot - m_PushConstants.size() < m_Uniforms.size()); + const Uniform& uniform = m_Uniforms[bindingSlot - m_PushConstants.size()]; + m_MaterialConstantsDataOutdated = true; + const uint32_t size = uniform.size; + const uint32_t offset = uniform.offset; + ENSURE(size <= dataSize); + return {m_MaterialConstantsData.get() + offset, size}; + } +} + +void CShaderProgram::SetTexture(const int32_t bindingSlot, CTexture* texture) +{ + if (bindingSlot < 0) + return; + CDescriptorManager& descriptorManager = m_Device->GetDescriptorManager(); + if (descriptorManager.UseDescriptorIndexing()) + { + const uint32_t descriptorIndex = descriptorManager.GetTextureDescriptor(texture->As()); + ENSURE(bindingSlot < static_cast(m_PushConstants.size())); + + const uint32_t size = m_PushConstants[bindingSlot].size; + const uint32_t offset = m_PushConstants[bindingSlot].offset; + ENSURE(size == sizeof(descriptorIndex)); + std::memcpy(m_PushConstantData.data() + offset, &descriptorIndex, size); + m_PushConstantDataMask |= ((1 << (size >> 2)) - 1) << (offset >> 2); + } + else + { + ENSURE(bindingSlot >= static_cast(m_PushConstants.size() + m_UniformMapping.size())); + const uint32_t index = bindingSlot - (m_PushConstants.size() + m_UniformMapping.size()); + if (m_BoundTexturesUID[index] != texture->GetUID()) + { + m_BoundTextures[index] = texture; + m_BoundTexturesUID[index] = texture->GetUID(); + m_BoundTexturesOutdated = true; + } + } +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/SubmitScheduler.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/SubmitScheduler.h +++ ps/trunk/source/renderer/backend/vulkan/SubmitScheduler.h @@ -0,0 +1,117 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SUBMITSCHEDULER +#define INCLUDED_RENDERER_BACKEND_VULKAN_SUBMITSCHEDULER + +#include "renderer/backend/vulkan/Device.h" + +#include +#include +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; +class CRingCommandContext; +class CSwapChain; + +/** + * A helper class to batch VkQueueSubmit calls and track VkCommandBuffer usages + * properly. + */ +class CSubmitScheduler +{ +public: + using SubmitHandle = uint32_t; + static constexpr SubmitHandle INVALID_SUBMIT_HANDLE = 0; + + CSubmitScheduler(CDevice* device, const uint32_t queueFamilyIndex, VkQueue queue); + ~CSubmitScheduler(); + + bool AcquireNextImage(CSwapChain& swapChain); + + void Present(CSwapChain& swapChain); + + SubmitHandle Submit(VkCommandBuffer commandBuffer); + + void WaitUntilFree(const SubmitHandle handle); + + uint32_t GetFrameID() const { return m_FrameID; } + +private: + void Flush(); + + CDevice* m_Device = nullptr; + VkQueue m_Queue = VK_NULL_HANDLE; + + struct Fence + { + VkFence value = VK_NULL_HANDLE; + SubmitHandle lastUsedHandle = INVALID_SUBMIT_HANDLE; + bool inUse = false; + }; + std::vector m_Fences; + uint32_t m_FenceIndex = 0; + + // We assume that we won't run so long that the frame ID will overflow. + uint32_t m_FrameID = 0; + SubmitHandle m_CurrentHandle = INVALID_SUBMIT_HANDLE + 1; + struct SubmittedHandle + { + SubmitHandle value; + uint32_t fenceIndex; + }; + std::queue m_SubmittedHandles; + + // We can't reuse frame data immediately after present because it might + // still be processing on GPU. + struct FrameObject + { + // We need to wait for the image on GPU to draw to it. + VkSemaphore acquireImageSemaphore = VK_NULL_HANDLE; + // We need to present only after all submit work is done. + VkSemaphore submitDone = VK_NULL_HANDLE; + }; + std::array m_FrameObjects; + + VkSemaphore m_NextWaitSemaphore = VK_NULL_HANDLE; + VkPipelineStageFlags m_NextWaitDstStageMask = 0; + VkSemaphore m_NextSubmitSignalSemaphore = VK_NULL_HANDLE; + + std::vector m_SubmittedCommandBuffers; + + std::unique_ptr m_AcquireCommandContext; + std::unique_ptr m_PresentCommandContext; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SUBMITSCHEDULER Index: ps/trunk/source/renderer/backend/vulkan/SubmitScheduler.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/SubmitScheduler.cpp +++ ps/trunk/source/renderer/backend/vulkan/SubmitScheduler.cpp @@ -0,0 +1,177 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "SubmitScheduler.h" + +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/RingCommandContext.h" +#include "renderer/backend/vulkan/SwapChain.h" +#include "renderer/backend/vulkan/Utilities.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +CSubmitScheduler::CSubmitScheduler( + CDevice* device, const uint32_t queueFamilyIndex, VkQueue queue) + : m_Device(device), m_Queue(queue) +{ + constexpr size_t numberOfFences = NUMBER_OF_FRAMES_IN_FLIGHT; + m_Fences.reserve(numberOfFences); + for (size_t index = 0; index < numberOfFences; ++index) + { + VkFenceCreateInfo fenceCreateInfo{}; + fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + VkFence fence = VK_NULL_HANDLE; + ENSURE_VK_SUCCESS(vkCreateFence( + m_Device->GetVkDevice(), &fenceCreateInfo, nullptr, &fence)); + m_Fences.push_back({fence, INVALID_SUBMIT_HANDLE}); + } + + for (FrameObject& frameObject : m_FrameObjects) + { + VkSemaphoreCreateInfo semaphoreCreateInfo{}; + semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + ENSURE_VK_SUCCESS(vkCreateSemaphore( + device->GetVkDevice(), &semaphoreCreateInfo, nullptr, &frameObject.acquireImageSemaphore)); + + ENSURE_VK_SUCCESS(vkCreateSemaphore( + device->GetVkDevice(), &semaphoreCreateInfo, nullptr, &frameObject.submitDone)); + } + + m_AcquireCommandContext = std::make_unique( + device, NUMBER_OF_FRAMES_IN_FLIGHT, queueFamilyIndex, *this); + m_PresentCommandContext = std::make_unique( + device, NUMBER_OF_FRAMES_IN_FLIGHT, queueFamilyIndex, *this); +} + +CSubmitScheduler::~CSubmitScheduler() +{ + VkDevice device = m_Device->GetVkDevice(); + + for (Fence& fence : m_Fences) + if (fence.value != VK_NULL_HANDLE) + vkDestroyFence(device, fence.value, nullptr); + + for (FrameObject& frameObject : m_FrameObjects) + { + if (frameObject.acquireImageSemaphore != VK_NULL_HANDLE) + vkDestroySemaphore(device, frameObject.acquireImageSemaphore, nullptr); + + if (frameObject.submitDone != VK_NULL_HANDLE) + vkDestroySemaphore(device, frameObject.submitDone, nullptr); + } +} + +bool CSubmitScheduler::AcquireNextImage(CSwapChain& swapChain) +{ + FrameObject& frameObject = m_FrameObjects[m_FrameID % m_FrameObjects.size()]; + if (!swapChain.AcquireNextImage(frameObject.acquireImageSemaphore)) + return false; + swapChain.SubmitCommandsAfterAcquireNextImage(*m_AcquireCommandContext); + + m_NextWaitSemaphore = frameObject.acquireImageSemaphore; + m_NextWaitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + m_AcquireCommandContext->Flush(); + return true; +} + +void CSubmitScheduler::Present(CSwapChain& swapChain) +{ + FrameObject& frameObject = m_FrameObjects[m_FrameID % m_FrameObjects.size()]; + swapChain.SubmitCommandsBeforePresent(*m_PresentCommandContext); + m_NextSubmitSignalSemaphore = frameObject.submitDone; + m_PresentCommandContext->Flush(); + Flush(); + swapChain.Present(frameObject.submitDone, m_Queue); +} + +CSubmitScheduler::SubmitHandle CSubmitScheduler::Submit(VkCommandBuffer commandBuffer) +{ + m_SubmittedCommandBuffers.emplace_back(commandBuffer); + return m_CurrentHandle; +} + +void CSubmitScheduler::WaitUntilFree(const SubmitHandle handle) +{ + // We haven't submitted the current handle. + if (handle == m_CurrentHandle) + Flush(); + + VkDevice device = m_Device->GetVkDevice(); + while (!m_SubmittedHandles.empty() && handle >= m_SubmittedHandles.front().value) + { + Fence& fence = m_Fences[m_SubmittedHandles.front().fenceIndex]; + ENSURE(fence.inUse); + m_SubmittedHandles.pop(); + ENSURE_VK_SUCCESS(vkWaitForFences(device, 1, &fence.value, VK_TRUE, std::numeric_limits::max())); + ENSURE_VK_SUCCESS(vkResetFences(device, 1, &fence.value)); + fence.inUse = false; + fence.lastUsedHandle = INVALID_SUBMIT_HANDLE; + } +} + +void CSubmitScheduler::Flush() +{ + ENSURE(!m_SubmittedCommandBuffers.empty()); + + Fence& fence = m_Fences[m_FenceIndex]; + if (fence.inUse) + WaitUntilFree(fence.lastUsedHandle); + fence.lastUsedHandle = m_CurrentHandle; + fence.inUse = true; + m_SubmittedHandles.push({m_CurrentHandle, m_FenceIndex}); + ++m_CurrentHandle; + m_FenceIndex = (m_FenceIndex + 1) % m_Fences.size(); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + if (m_NextWaitSemaphore != VK_NULL_HANDLE) + { + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &m_NextWaitSemaphore; + submitInfo.pWaitDstStageMask = &m_NextWaitDstStageMask; + } + if (m_NextSubmitSignalSemaphore != VK_NULL_HANDLE) + { + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &m_NextSubmitSignalSemaphore; + } + submitInfo.commandBufferCount = m_SubmittedCommandBuffers.size(); + submitInfo.pCommandBuffers = m_SubmittedCommandBuffers.data(); + + ENSURE_VK_SUCCESS(vkQueueSubmit(m_Queue, 1, &submitInfo, fence.value)); + + m_NextWaitSemaphore = VK_NULL_HANDLE; + m_NextWaitDstStageMask = 0; + m_NextSubmitSignalSemaphore = VK_NULL_HANDLE; + + m_SubmittedCommandBuffers.clear(); +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/SwapChain.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/SwapChain.h +++ ps/trunk/source/renderer/backend/vulkan/SwapChain.h @@ -0,0 +1,115 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN +#define INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN + +#include "renderer/backend/IFramebuffer.h" + +#include +#include +#include +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; +class CFramebuffer; +class CRingCommandContext; +class CTexture; + +class CSwapChain final +{ +public: + ~CSwapChain(); + + VkSwapchainKHR GetVkSwapchain() { return m_SwapChain; } + + bool IsValid() const { return m_IsValid; } + + bool AcquireNextImage(VkSemaphore acquireImageSemaphore); + void SubmitCommandsAfterAcquireNextImage( + CRingCommandContext& commandContext); + void SubmitCommandsBeforePresent( + CRingCommandContext& commandContext); + void Present(VkSemaphore submitDone, VkQueue queue); + + CFramebuffer* GetCurrentBackbuffer( + const AttachmentLoadOp colorAttachmentLoadOp, + const AttachmentStoreOp colorAttachmentStoreOp, + const AttachmentLoadOp depthStencilAttachmentLoadOp, + const AttachmentStoreOp depthStencilAttachmentStoreOp); + +private: + friend class CDevice; + + static std::unique_ptr Create( + CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight, + std::unique_ptr oldSwapChain); + + CSwapChain(); + + CDevice* m_Device = nullptr; + + bool m_IsValid = false; + VkSwapchainKHR m_SwapChain = VK_NULL_HANDLE; + + uint32_t m_CurrentImageIndex = std::numeric_limits::max(); + + std::vector m_Images; + std::vector> m_Textures; + std::unique_ptr m_DepthTexture; + VkFormat m_ImageFormat = VK_FORMAT_UNDEFINED; + + struct SwapChainBackbuffer + { + using BackbufferKey = std::tuple< + AttachmentLoadOp, AttachmentStoreOp, + AttachmentLoadOp, AttachmentStoreOp>; + struct BackbufferKeyHash + { + size_t operator()(const BackbufferKey& key) const; + }; + std::unordered_map< + BackbufferKey, std::unique_ptr, BackbufferKeyHash> backbuffers; + + SwapChainBackbuffer(); + + SwapChainBackbuffer(const SwapChainBackbuffer&) = delete; + SwapChainBackbuffer& operator=(const SwapChainBackbuffer&) = delete; + + SwapChainBackbuffer(SwapChainBackbuffer&& other); + SwapChainBackbuffer& operator=(SwapChainBackbuffer&& other); + }; + std::vector m_Backbuffers; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN Index: ps/trunk/source/renderer/backend/vulkan/SwapChain.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/SwapChain.cpp +++ ps/trunk/source/renderer/backend/vulkan/SwapChain.cpp @@ -0,0 +1,365 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "SwapChain.h" + +#include "lib/hash.h" +#include "maths/MathUtil.h" +#include "ps/ConfigDB.h" +#include "ps/Profile.h" +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Framebuffer.h" +#include "renderer/backend/vulkan/RingCommandContext.h" +#include "renderer/backend/vulkan/Texture.h" +#include "renderer/backend/vulkan/Utilities.h" + +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +// static +std::unique_ptr CSwapChain::Create( + CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight, + std::unique_ptr oldSwapChain) +{ + std::unique_ptr swapChain(new CSwapChain()); + swapChain->m_Device = device; + + VkPhysicalDevice physicalDevice = device->GetChoosenPhysicalDevice().device; + + VkSurfaceCapabilitiesKHR surfaceCapabilities{}; + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physicalDevice, surface, &surfaceCapabilities)); + + std::vector surfaceFormats; + uint32_t surfaceFormatCount = 0; + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, surface, &surfaceFormatCount, nullptr)); + if (surfaceFormatCount > 0) + { + surfaceFormats.resize(surfaceFormatCount); + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, surface, &surfaceFormatCount, surfaceFormats.data())); + } + + std::vector presentModes; + uint32_t presentModeCount = 0; + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfacePresentModesKHR( + physicalDevice, surface, &presentModeCount, nullptr)); + if (presentModeCount > 0) + { + presentModes.resize(presentModeCount); + ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfacePresentModesKHR( + physicalDevice, surface, &presentModeCount, presentModes.data())); + } + + // VK_PRESENT_MODE_FIFO_KHR is guaranteed to be supported. + VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; + auto isPresentModeAvailable = [&presentModes](const VkPresentModeKHR presentMode) + { + return std::find(presentModes.begin(), presentModes.end(), presentMode) != presentModes.end(); + }; + bool vsyncEnabled = true; + CFG_GET_VAL("vsync", vsyncEnabled); + if (vsyncEnabled) + { + // TODO: use the adaptive one when possible. + // https://gitlab.freedesktop.org/mesa/mesa/-/issues/5516 + //if (isPresentModeAvailable(VK_PRESENT_MODE_MAILBOX_KHR)) + // presentMode = VK_PRESENT_MODE_MAILBOX_KHR; + } + else + { + if (isPresentModeAvailable(VK_PRESENT_MODE_IMMEDIATE_KHR)) + presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + } + + // Spec says: + // The number of format pairs supported must be greater than or equal to 1. + // pSurfaceFormats must not contain an entry whose value for format is + // VK_FORMAT_UNDEFINED. + const auto surfaceFormatIt = + std::find_if(surfaceFormats.begin(), surfaceFormats.end(), IsSurfaceFormatSupported); + if (surfaceFormatIt == surfaceFormats.end()) + { + LOGERROR("Can't find a suitable surface format to render to."); + return nullptr; + } + const VkSurfaceFormatKHR& surfaceFormat = *surfaceFormatIt; + + const uint32_t swapChainWidth = Clamp(surfaceDrawableWidth, + surfaceCapabilities.minImageExtent.width, + surfaceCapabilities.maxImageExtent.width); + const uint32_t swapChainHeight = Clamp(surfaceDrawableHeight, + surfaceCapabilities.minImageExtent.height, + surfaceCapabilities.maxImageExtent.height); + + VkSwapchainCreateInfoKHR swapChainCreateInfo{}; + swapChainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapChainCreateInfo.surface = surface; + // minImageCount + 1 is to have a less chance for a presenter to wait. + // maxImageCount might be zero, it means it's unlimited. + swapChainCreateInfo.minImageCount = + Clamp(NUMBER_OF_FRAMES_IN_FLIGHT, + surfaceCapabilities.minImageCount + 1, + surfaceCapabilities.maxImageCount > 0 + ? surfaceCapabilities.maxImageCount + : std::numeric_limits::max()); + swapChainCreateInfo.imageFormat = surfaceFormat.format; + swapChainCreateInfo.imageColorSpace = surfaceFormat.colorSpace; + swapChainCreateInfo.imageExtent.width = swapChainWidth; + swapChainCreateInfo.imageExtent.height = swapChainHeight; + swapChainCreateInfo.imageArrayLayers = 1; + // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT is guaranteed to present. + // VK_IMAGE_USAGE_TRANSFER_SRC_BIT allows a simpler backbuffer readback. + // VK_IMAGE_USAGE_TRANSFER_DST_BIT allows a blit to the backbuffer. + swapChainCreateInfo.imageUsage = + (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT) & + surfaceCapabilities.supportedUsageFlags; + swapChainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + // We need to set these only if imageSharingMode is VK_SHARING_MODE_CONCURRENT. + swapChainCreateInfo.queueFamilyIndexCount = 0; + swapChainCreateInfo.pQueueFamilyIndices = nullptr; + // By default VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR is preferable. + if (surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) + swapChainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + else + swapChainCreateInfo.preTransform = surfaceCapabilities.currentTransform; + // By default VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR is preferable, other bits + // might require some format or rendering adjustemnts to avoid + // semi-transparent areas. + const VkCompositeAlphaFlagBitsKHR compositeAlphaOrder[] = + { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR + }; + for (const VkCompositeAlphaFlagBitsKHR compositeAlpha : compositeAlphaOrder) + if (compositeAlpha & surfaceCapabilities.supportedCompositeAlpha) + { + swapChainCreateInfo.compositeAlpha = compositeAlpha; + break; + } + swapChainCreateInfo.presentMode = presentMode; + swapChainCreateInfo.clipped = VK_TRUE; + if (oldSwapChain) + swapChainCreateInfo.oldSwapchain = oldSwapChain->GetVkSwapchain(); + + ENSURE_VK_SUCCESS(vkCreateSwapchainKHR( + device->GetVkDevice(), &swapChainCreateInfo, nullptr, &swapChain->m_SwapChain)); + + char nameBuffer[64]; + snprintf(nameBuffer, std::size(nameBuffer), "SwapChain: %dx%d", surfaceDrawableWidth, surfaceDrawableHeight); + device->SetObjectName(VK_OBJECT_TYPE_SWAPCHAIN_KHR, swapChain->m_SwapChain, nameBuffer); + + uint32_t imageCount = 0; + ENSURE_VK_SUCCESS(vkGetSwapchainImagesKHR( + device->GetVkDevice(), swapChain->m_SwapChain, &imageCount, nullptr)); + swapChain->m_Images.resize(imageCount); + ENSURE_VK_SUCCESS(vkGetSwapchainImagesKHR( + device->GetVkDevice(), swapChain->m_SwapChain, &imageCount, swapChain->m_Images.data())); + + swapChain->m_DepthTexture = CTexture::Create( + device, "SwapChainDepthTexture", ITexture::Type::TEXTURE_2D, + ITexture::Usage::DEPTH_STENCIL_ATTACHMENT, Format::D24_S8, + swapChainWidth, swapChainHeight, Sampler::MakeDefaultSampler( + Sampler::Filter::NEAREST, Sampler::AddressMode::CLAMP_TO_EDGE), + 1, 1); + + swapChain->m_ImageFormat = swapChainCreateInfo.imageFormat; + + swapChain->m_Textures.resize(imageCount); + swapChain->m_Backbuffers.resize(imageCount); + for (size_t index = 0; index < imageCount; ++index) + { + snprintf(nameBuffer, std::size(nameBuffer), "SwapChainImage #%zu", index); + device->SetObjectName(VK_OBJECT_TYPE_IMAGE, swapChain->m_Images[index], nameBuffer); + snprintf(nameBuffer, std::size(nameBuffer), "SwapChainImageView #%zu", index); + swapChain->m_Textures[index] = CTexture::WrapBackbufferImage( + device, nameBuffer, swapChain->m_Images[index], swapChainCreateInfo.imageFormat, + swapChainCreateInfo.imageUsage, swapChainWidth, swapChainHeight); + } + + swapChain->m_IsValid = true; + + return swapChain; +} + +CSwapChain::CSwapChain() = default; + +CSwapChain::~CSwapChain() +{ + m_Backbuffers.clear(); + + m_Textures.clear(); + m_DepthTexture.reset(); + + if (m_SwapChain != VK_NULL_HANDLE) + vkDestroySwapchainKHR(m_Device->GetVkDevice(), m_SwapChain, nullptr); +} + +size_t CSwapChain::SwapChainBackbuffer::BackbufferKeyHash::operator()(const BackbufferKey& key) const +{ + size_t seed = 0; + hash_combine(seed, std::get<0>(key)); + hash_combine(seed, std::get<1>(key)); + hash_combine(seed, std::get<2>(key)); + hash_combine(seed, std::get<3>(key)); + return seed; +} + +CSwapChain::SwapChainBackbuffer::SwapChainBackbuffer() = default; + +CSwapChain::SwapChainBackbuffer::SwapChainBackbuffer(SwapChainBackbuffer&& other) = default; + +CSwapChain::SwapChainBackbuffer& CSwapChain::SwapChainBackbuffer::operator=(SwapChainBackbuffer&& other) = default; + +bool CSwapChain::AcquireNextImage(VkSemaphore acquireImageSemaphore) +{ + ENSURE(m_CurrentImageIndex == std::numeric_limits::max()); + + const VkResult acquireResult = vkAcquireNextImageKHR( + m_Device->GetVkDevice(), m_SwapChain, std::numeric_limits::max(), + acquireImageSemaphore, + VK_NULL_HANDLE, &m_CurrentImageIndex); + if (acquireResult != VK_SUCCESS) + { + if (acquireResult == VK_ERROR_OUT_OF_DATE_KHR) + m_IsValid = false; + else if (acquireResult != VK_SUBOPTIMAL_KHR) + { + LOGERROR("Acquire result: %d", static_cast(acquireResult)); + debug_warn("Unknown acquire error."); + } + } + return m_IsValid; +} + +void CSwapChain::SubmitCommandsAfterAcquireNextImage( + CRingCommandContext& commandContext) +{ + const bool firstAcquirement = !m_Textures[m_CurrentImageIndex]->IsInitialized(); + Utilities::SubmitImageMemoryBarrier( + commandContext.GetCommandBuffer(), + m_Images[m_CurrentImageIndex], 0, 0, + 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + firstAcquirement ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + firstAcquirement ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + if (!m_DepthTexture->IsInitialized()) + { + Utilities::SubmitImageMemoryBarrier( + commandContext.GetCommandBuffer(), + m_DepthTexture->GetImage(), 0, 0, + 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + } +} + +void CSwapChain::SubmitCommandsBeforePresent( + CRingCommandContext& commandContext) +{ + ENSURE(m_CurrentImageIndex != std::numeric_limits::max()); + + Utilities::SubmitImageMemoryBarrier( + commandContext.GetCommandBuffer(), m_Images[m_CurrentImageIndex], 0, 0, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); +} + +void CSwapChain::Present(VkSemaphore submitDone, VkQueue queue) +{ + ENSURE(m_CurrentImageIndex != std::numeric_limits::max()); + + VkSwapchainKHR swapChains[] = {m_SwapChain}; + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &m_CurrentImageIndex; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &submitDone; + const VkResult presentResult = vkQueuePresentKHR(queue, &presentInfo); + if (presentResult != VK_SUCCESS) + { + if (presentResult == VK_ERROR_OUT_OF_DATE_KHR) + m_IsValid = false; + else if (presentResult != VK_SUBOPTIMAL_KHR) + { + LOGERROR("Present result: %d", static_cast(presentResult)); + debug_warn("Unknown present error."); + } + } + + m_CurrentImageIndex = std::numeric_limits::max(); +} + +CFramebuffer* CSwapChain::GetCurrentBackbuffer( + const AttachmentLoadOp colorAttachmentLoadOp, + const AttachmentStoreOp colorAttachmentStoreOp, + const AttachmentLoadOp depthStencilAttachmentLoadOp, + const AttachmentStoreOp depthStencilAttachmentStoreOp) +{ + ENSURE(m_CurrentImageIndex != std::numeric_limits::max()); + SwapChainBackbuffer& swapChainBackbuffer = + m_Backbuffers[m_CurrentImageIndex]; + const SwapChainBackbuffer::BackbufferKey key{ + colorAttachmentLoadOp, colorAttachmentStoreOp, + depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp}; + auto it = swapChainBackbuffer.backbuffers.find(key); + if (it == swapChainBackbuffer.backbuffers.end()) + { + char nameBuffer[64]; + snprintf(nameBuffer, std::size(nameBuffer), "Backbuffer #%u", m_CurrentImageIndex); + + SColorAttachment colorAttachment{}; + colorAttachment.texture = m_Textures[m_CurrentImageIndex].get(); + colorAttachment.loadOp = colorAttachmentLoadOp; + colorAttachment.storeOp = colorAttachmentStoreOp; + + SDepthStencilAttachment depthStencilAttachment{}; + depthStencilAttachment.texture = m_DepthTexture.get(); + depthStencilAttachment.loadOp = depthStencilAttachmentLoadOp; + depthStencilAttachment.storeOp = depthStencilAttachmentStoreOp; + + it = swapChainBackbuffer.backbuffers.emplace(key, CFramebuffer::Create( + m_Device, nameBuffer, &colorAttachment, &depthStencilAttachment)).first; + } + return it->second.get(); +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Texture.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Texture.h +++ ps/trunk/source/renderer/backend/vulkan/Texture.h @@ -0,0 +1,132 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_TEXTURE +#define INCLUDED_RENDERER_BACKEND_VULKAN_TEXTURE + +#include "renderer/backend/ITexture.h" +#include "renderer/backend/Sampler.h" +#include "renderer/backend/vulkan/VMA.h" + +#include +#include + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CDevice; + +class CTexture final : public ITexture +{ +public: + ~CTexture() override; + + IDevice* GetDevice() override; + + Type GetType() const override { return m_Type; } + uint32_t GetUsage() const override { return m_Usage; } + Format GetFormat() const override { return m_Format; } + + uint32_t GetWidth() const override { return m_Width; } + uint32_t GetHeight() const override { return m_Height; } + uint32_t GetMIPLevelCount() const override { return m_MIPLevelCount; } + uint32_t GetSampleCount() const { return m_SampleCount; } + uint32_t GetLayerCount() const { return m_LayerCount; } + + VkImage GetImage() { return m_Image; } + VkImageView GetAttachmentImageView() { return m_AttachmentImageView; } + VkImageView GetSamplerImageView() { return m_SamplerImageView; } + VkSampler GetSampler() { return m_Sampler; } + bool IsCompareEnabled() { return m_IsCompareEnabled; } + VkFormat GetVkFormat() const { return m_VkFormat; } + + VkImageAspectFlags GetAttachmentImageAspectMask() { return m_AttachmentImageAspectMask; } + VkImageAspectFlags GetSamplerImageAspectMask() { return m_SamplerImageAspectMask; } + + bool IsInitialized() const { return m_Initialized; } + void SetInitialized() { m_Initialized = true; } + + /** + * @return UID of the texture. It's unique along all textures during a whole + * application run. We assume that 32bits should be enough, else we'd have + * a too big texture flow. + */ + using UID = uint32_t; + UID GetUID() const { return m_UID; } + +private: + friend class CDevice; + friend class CSwapChain; + + CTexture(); + + static std::unique_ptr Create( + CDevice* device, const char* name, const Type type, const uint32_t usage, + const Format format, const uint32_t width, const uint32_t height, + const Sampler::Desc& defaultSamplerDesc, + const uint32_t MIPLevelCount, const uint32_t sampleCount); + + static std::unique_ptr WrapBackbufferImage( + CDevice* device, const char* name, const VkImage image, const VkFormat format, + const VkImageUsageFlags usage, const uint32_t width, const uint32_t height); + + Type m_Type = Type::TEXTURE_2D; + uint32_t m_Usage = 0; + Format m_Format = Format::UNDEFINED; + VkFormat m_VkFormat = VK_FORMAT_UNDEFINED; + uint32_t m_Width = 0; + uint32_t m_Height = 0; + uint32_t m_MIPLevelCount = 0; + uint32_t m_SampleCount = 0; + uint32_t m_LayerCount = 0; + + CDevice* m_Device = nullptr; + + VkImage m_Image = VK_NULL_HANDLE; + VkImageView m_AttachmentImageView = VK_NULL_HANDLE; + VkImageView m_SamplerImageView = VK_NULL_HANDLE; + VkSampler m_Sampler = VK_NULL_HANDLE; + bool m_IsCompareEnabled = false; + VmaAllocation m_Allocation{}; + + UID m_UID = 0; + + // Sampler image aspect mask is submask of the attachment one. As we can't + // have both VK_IMAGE_ASPECT_DEPTH_BIT and VK_IMAGE_ASPECT_STENCIL_BIT for + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + VkImageAspectFlags m_AttachmentImageAspectMask = 0; + VkImageAspectFlags m_SamplerImageAspectMask = 0; + + // We store a flag of all subresources, we don't have to handle them separately. + // It's safe to store the current state while we use a single device command + // context. + bool m_Initialized = false; +}; + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_TEXTURE Index: ps/trunk/source/renderer/backend/vulkan/Texture.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Texture.cpp +++ ps/trunk/source/renderer/backend/vulkan/Texture.cpp @@ -0,0 +1,298 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "Texture.h" + +#include "renderer/backend/vulkan/Device.h" +#include "renderer/backend/vulkan/Mapping.h" +#include "renderer/backend/vulkan/SamplerManager.h" +#include "renderer/backend/vulkan/Utilities.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +// static +std::unique_ptr CTexture::Create( + CDevice* device, const char* name, const Type type, const uint32_t usage, + const Format format, const uint32_t width, const uint32_t height, + const Sampler::Desc& defaultSamplerDesc, + const uint32_t MIPLevelCount, const uint32_t sampleCount) +{ + std::unique_ptr texture(new CTexture()); + texture->m_Device = device; + + texture->m_Format = format; + texture->m_Type = type; + texture->m_Usage = usage; + texture->m_Width = width; + texture->m_Height = height; + texture->m_MIPLevelCount = MIPLevelCount; + texture->m_SampleCount = sampleCount; + texture->m_LayerCount = type == ITexture::Type::TEXTURE_CUBE ? 6 : 1; + + if (type == Type::TEXTURE_2D_MULTISAMPLE) + ENSURE(sampleCount > 1); + + VkFormat imageFormat = VK_FORMAT_UNDEFINED; + // A8 and L8 are special cases for GL2.1, because it doesn't have a proper + // channel swizzling. + if (format == Format::A8_UNORM || format == Format::L8_UNORM) + imageFormat = VK_FORMAT_R8_UNORM; + else + imageFormat = Mapping::FromFormat(format); + texture->m_VkFormat = imageFormat; + + VkImageType imageType = VK_IMAGE_TYPE_2D; + VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; + + const VkPhysicalDevice physicalDevice = + device->GetChoosenPhysicalDevice().device; + + VkFormatProperties formatProperties{}; + vkGetPhysicalDeviceFormatProperties( + physicalDevice, imageFormat, &formatProperties); + + VkImageUsageFlags usageFlags = 0; + // Vulkan 1.0 implies that TRANSFER_SRC and TRANSFER_DST are supported. + if (usage & Usage::TRANSFER_SRC) + usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (usage & Usage::TRANSFER_DST) + usageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (usage & Usage::SAMPLED) + { + ENSURE(type != Type::TEXTURE_2D_MULTISAMPLE); + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) + { + LOGERROR("Format %d doesn't support sampling for optimal tiling.", static_cast(imageFormat)); + return nullptr; + } + usageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT; + } + if (usage & Usage::COLOR_ATTACHMENT) + { + ENSURE(device->IsFramebufferFormatSupported(format)); + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) + { + LOGERROR("Format %d doesn't support color attachment for optimal tiling.", static_cast(imageFormat)); + return nullptr; + } + usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + if (usage & Usage::DEPTH_STENCIL_ATTACHMENT) + { + ENSURE(IsDepthFormat(format)); + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) + { + LOGERROR("Format %d doesn't support depth stencil attachment for optimal tiling.", static_cast(imageFormat)); + return nullptr; + } + usageFlags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } + + if (IsDepthFormat(format)) + { + texture->m_AttachmentImageAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + texture->m_SamplerImageAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (format == Format::D24_S8) + texture->m_AttachmentImageAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + else + { + texture->m_AttachmentImageAspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + texture->m_SamplerImageAspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + VkImageCreateInfo imageCreateInfo{}; + imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageCreateInfo.imageType = imageType; + imageCreateInfo.extent.width = width; + imageCreateInfo.extent.height = height; + imageCreateInfo.extent.depth = 1; + imageCreateInfo.mipLevels = MIPLevelCount; + imageCreateInfo.arrayLayers = type == Type::TEXTURE_CUBE ? 6 : 1; + imageCreateInfo.format = imageFormat; + imageCreateInfo.samples = Mapping::FromSampleCount(sampleCount); + imageCreateInfo.tiling = tiling; + imageCreateInfo.usage = usageFlags; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if (type == Type::TEXTURE_CUBE) + imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + VmaAllocationCreateInfo allocationCreateInfo{}; + if ((usage & Usage::COLOR_ATTACHMENT) || (usage & Usage::DEPTH_STENCIL_ATTACHMENT)) + allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; +#ifndef NDEBUG + allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT; + allocationCreateInfo.pUserData = const_cast(name); +#endif + allocationCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + allocationCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + const VkResult createImageResult = vmaCreateImage( + device->GetVMAAllocator(), &imageCreateInfo, &allocationCreateInfo, + &texture->m_Image, &texture->m_Allocation, nullptr); + if (createImageResult != VK_SUCCESS) + { + LOGERROR("Failed to create VkImage: %d", static_cast(createImageResult)); + return nullptr; + } + + VkImageViewCreateInfo imageViewCreateInfo{}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = texture->m_Image; + imageViewCreateInfo.viewType = type == Type::TEXTURE_CUBE ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = imageFormat; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = MIPLevelCount; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = type == Type::TEXTURE_CUBE ? 6 : 1; + if (format == Format::A8_UNORM) + { + imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_ZERO; + imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_ZERO; + imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_ZERO; + imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_R; + } + else if (format == Format::L8_UNORM) + { + imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_R; + imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_R; + imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_R; + imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_ONE; + } + else + { + imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + } + + imageViewCreateInfo.subresourceRange.aspectMask = texture->m_AttachmentImageAspectMask; + ENSURE_VK_SUCCESS(vkCreateImageView( + device->GetVkDevice(), &imageViewCreateInfo, nullptr, &texture->m_AttachmentImageView)); + imageViewCreateInfo.subresourceRange.aspectMask = texture->m_SamplerImageAspectMask; + ENSURE_VK_SUCCESS(vkCreateImageView( + device->GetVkDevice(), &imageViewCreateInfo, nullptr, &texture->m_SamplerImageView)); + + if (usage & Usage::SAMPLED) + { + texture->m_Sampler = device->GetSamplerManager().GetOrCreateSampler( + defaultSamplerDesc); + texture->m_IsCompareEnabled = defaultSamplerDesc.compareEnabled; + } + + device->SetObjectName(VK_OBJECT_TYPE_IMAGE, texture->m_Image, name); + if (texture->m_AttachmentImageView != VK_NULL_HANDLE) + device->SetObjectName(VK_OBJECT_TYPE_IMAGE_VIEW, texture->m_AttachmentImageView, name); + if (texture->m_SamplerImageView != VK_NULL_HANDLE) + device->SetObjectName(VK_OBJECT_TYPE_IMAGE_VIEW, texture->m_SamplerImageView, name); + + return texture; +} + +// static +std::unique_ptr CTexture::WrapBackbufferImage( + CDevice* device, const char* name, const VkImage image, const VkFormat format, + const VkImageUsageFlags usage, const uint32_t width, const uint32_t height) +{ + std::unique_ptr texture(new CTexture()); + texture->m_Device = device; + + texture->m_Format = Format::UNDEFINED; + texture->m_Type = Type::TEXTURE_2D; + if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) + texture->m_Usage |= Usage::COLOR_ATTACHMENT; + if (usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) + texture->m_Usage |= Usage::TRANSFER_SRC; + if (usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) + texture->m_Usage |= Usage::TRANSFER_DST; + texture->m_Width = width; + texture->m_Height = height; + texture->m_MIPLevelCount = 1; + texture->m_SampleCount = 1; + texture->m_LayerCount = 1; + texture->m_VkFormat = format; + // The image is owned by its swapchain, but we don't set a special flag + // because the ownership is detected by m_Allocation presence. + texture->m_Image = image; + texture->m_AttachmentImageAspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + texture->m_SamplerImageAspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + VkImageViewCreateInfo imageViewCreateInfo{}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = image; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = format; + imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = 1; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + ENSURE_VK_SUCCESS(vkCreateImageView( + device->GetVkDevice(), &imageViewCreateInfo, nullptr, &texture->m_AttachmentImageView)); + device->SetObjectName(VK_OBJECT_TYPE_IMAGE_VIEW, texture->m_AttachmentImageView, name); + + return texture; +} + +CTexture::CTexture() +{ + static uint32_t m_LastAvailableUID = 1; + m_UID = m_LastAvailableUID++; +} + +CTexture::~CTexture() +{ + if (m_AttachmentImageView != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_IMAGE_VIEW, m_AttachmentImageView, VK_NULL_HANDLE); + + if (m_SamplerImageView != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_IMAGE_VIEW, m_SamplerImageView, VK_NULL_HANDLE); + + if (m_Allocation != VK_NULL_HANDLE) + m_Device->ScheduleObjectToDestroy( + VK_OBJECT_TYPE_IMAGE, m_Image, m_Allocation); + + m_Device->ScheduleTextureToDestroy(m_UID); +} + +IDevice* CTexture::GetDevice() +{ + return m_Device; +} + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/Utilities.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Utilities.h +++ ps/trunk/source/renderer/backend/vulkan/Utilities.h @@ -0,0 +1,91 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_UTILITIES +#define INCLUDED_RENDERER_BACKEND_VULKAN_UTILITIES + +#include "ps/CStr.h" + +#include + +#define ENSURE_VK_SUCCESS(EXPR) \ + do \ + { \ + const VkResult result = (EXPR); \ + if (result != VK_SUCCESS) \ + { \ + LOGERROR(#EXPR " returned %d instead of VK_SUCCESS", static_cast(result)); \ + ENSURE(false && #EXPR); \ + } \ + } while (0) + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +class CBuffer; +class CTexture; + +namespace Utilities +{ + +// https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples-(Legacy-synchronization-APIs) + +void SetTextureLayout( + VkCommandBuffer commandBuffer, CTexture* texture, + const VkImageLayout oldLayout, const VkImageLayout newLayout, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask); + +void SubmitImageMemoryBarrier( + VkCommandBuffer commandBuffer, VkImage image, const uint32_t level, const uint32_t layer, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkImageLayout oldLayout, const VkImageLayout newLayout, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask, + const VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT); + +void SubmitBufferMemoryBarrier( + VkCommandBuffer commandBuffer, CBuffer* buffer, + const uint32_t offset, const uint32_t size, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask); + +void SubmitMemoryBarrier( + VkCommandBuffer commandBuffer, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask); + +void SubmitPipelineBarrier( + VkCommandBuffer commandBuffer, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask); + +void SubmitDebugSyncMemoryBarrier(VkCommandBuffer commandBuffer); + +} // namespace Utilities + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_UTILITIES Index: ps/trunk/source/renderer/backend/vulkan/Utilities.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Utilities.cpp +++ ps/trunk/source/renderer/backend/vulkan/Utilities.cpp @@ -0,0 +1,170 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "Utilities.h" + +#include "lib/code_annotation.h" +#include "lib/config2.h" +#include "renderer/backend/vulkan/Buffer.h" +#include "renderer/backend/vulkan/Texture.h" + +namespace Renderer +{ + +namespace Backend +{ + +namespace Vulkan +{ + +namespace Utilities +{ + +void SetTextureLayout( + VkCommandBuffer commandBuffer, CTexture* texture, + const VkImageLayout oldLayout, const VkImageLayout newLayout, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask) +{ + ENSURE(texture->GetMIPLevelCount() == 1); + ENSURE(texture->GetLayerCount() == 1); + + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.image = texture->GetImage(); + imageMemoryBarrier.srcAccessMask = srcAccessMask; + imageMemoryBarrier.dstAccessMask = dstAccessMask; + imageMemoryBarrier.oldLayout = oldLayout; + imageMemoryBarrier.newLayout = newLayout; + imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemoryBarrier.subresourceRange.aspectMask = texture->GetAttachmentImageAspectMask(); + imageMemoryBarrier.subresourceRange.baseMipLevel = 0; + imageMemoryBarrier.subresourceRange.levelCount = texture->GetMIPLevelCount(); + imageMemoryBarrier.subresourceRange.baseArrayLayer = 0; + imageMemoryBarrier.subresourceRange.layerCount = texture->GetLayerCount(); + + vkCmdPipelineBarrier(commandBuffer, + srcStageMask, dstStageMask, 0, + 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); + + texture->SetInitialized(); +} + +void SubmitImageMemoryBarrier( + VkCommandBuffer commandBuffer, VkImage image, const uint32_t level, const uint32_t layer, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkImageLayout oldLayout, const VkImageLayout newLayout, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask, + const VkImageAspectFlags aspectMask) +{ + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.image = image; + imageMemoryBarrier.srcAccessMask = srcAccessMask; + imageMemoryBarrier.dstAccessMask = dstAccessMask; + imageMemoryBarrier.oldLayout = oldLayout; + imageMemoryBarrier.newLayout = newLayout; + imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemoryBarrier.subresourceRange.aspectMask = aspectMask; + imageMemoryBarrier.subresourceRange.baseMipLevel = level; + imageMemoryBarrier.subresourceRange.levelCount = 1; + imageMemoryBarrier.subresourceRange.baseArrayLayer = layer; + imageMemoryBarrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier(commandBuffer, + srcStageMask, dstStageMask, 0, + 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); +} + +void SubmitBufferMemoryBarrier( + VkCommandBuffer commandBuffer, CBuffer* buffer, + const uint32_t offset, const uint32_t size, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask) +{ + VkBufferMemoryBarrier bufferMemoryBarrier{}; + bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufferMemoryBarrier.srcAccessMask = srcAccessMask; + bufferMemoryBarrier.dstAccessMask = dstAccessMask; + bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufferMemoryBarrier.buffer = buffer->GetVkBuffer(); + bufferMemoryBarrier.offset = offset; + bufferMemoryBarrier.size = size; + + vkCmdPipelineBarrier( + commandBuffer, srcStageMask, dstStageMask, 0, + 0, nullptr, 1, &bufferMemoryBarrier, 0, nullptr); +} + +void SubmitMemoryBarrier( + VkCommandBuffer commandBuffer, + const VkAccessFlags srcAccessMask, const VkAccessFlags dstAccessMask, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask) +{ + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = srcAccessMask; + memoryBarrier.dstAccessMask = dstAccessMask; + vkCmdPipelineBarrier( + commandBuffer, srcStageMask, dstStageMask, 0, + 1, &memoryBarrier, 0, nullptr, 0, nullptr); +} + +void SubmitPipelineBarrier( + VkCommandBuffer commandBuffer, + const VkPipelineStageFlags srcStageMask, const VkPipelineStageFlags dstStageMask) +{ + vkCmdPipelineBarrier( + commandBuffer, srcStageMask, dstStageMask, 0, + 0, nullptr, 0, nullptr, 0, nullptr); +} + +void SubmitDebugSyncMemoryBarrier(VkCommandBuffer commandBuffer) +{ + const VkAccessFlags accessMask = + VK_ACCESS_INDIRECT_COMMAND_READ_BIT | + VK_ACCESS_INDEX_READ_BIT | + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | + VK_ACCESS_UNIFORM_READ_BIT | + VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | + VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_HOST_READ_BIT | + VK_ACCESS_HOST_WRITE_BIT; + SubmitMemoryBarrier( + commandBuffer, accessMask, accessMask, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); +} + +} // namespace Utilities + +} // namespace Vulkan + +} // namespace Backend + +} // namespace Renderer Index: ps/trunk/source/renderer/backend/vulkan/VMA.h =================================================================== --- ps/trunk/source/renderer/backend/vulkan/VMA.h +++ ps/trunk/source/renderer/backend/vulkan/VMA.h @@ -0,0 +1,80 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_VMA +#define INCLUDED_RENDERER_BACKEND_VULKAN_VMA + +#include "lib/debug.h" +#include "lib/sysdep/os.h" +#include "ps/CLogger.h" + +#include +#include + +#define VMA_VULKAN_VERSION 1000000 +#define VMA_ASSERT(EXPR) ASSERT(EXPR) +#define VMA_HEAVY_ASSERT(EXPR) ENSURE(EXPR) +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_BUFFER_DEVICE_ADDRESS 0 + +#ifndef NDEBUG +#define VMA_DEBUG_LOG(...) debug_printf(__VA_ARGS__) +#define VMA_STATS_STRING_ENABLED 1 +#else +#define VMA_DEBUG_LOG(...) +#define VMA_STATS_STRING_ENABLED 0 +#endif + +#if OS_WIN +// MSVC doesn't enable std::shared_mutex for XP toolkit. +#define VMA_USE_STL_SHARED_MUTEX 0 +#endif + +#if GCC_VERSION +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif +#if CLANG_VERSION +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat" +#pragma clang diagnostic ignored "-Wnullability-completeness" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wunused-variable" +#endif +#if MSC_VERSION +#pragma warning(push, 1) +#pragma warning(disable: 4100) +#endif + +#include "third_party/vma/vk_mem_alloc.h" + +#if GCC_VERSION +#pragma GCC diagnostic pop +#endif +#if CLANG_VERSION +#pragma clang diagnostic pop +#endif +#if MSC_VERSION +#pragma warning(pop) +#endif + +#endif // INCLUDED_RENDERER_BACKEND_VULKAN_VMA Index: ps/trunk/source/renderer/backend/vulkan/VMA.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/VMA.cpp +++ ps/trunk/source/renderer/backend/vulkan/VMA.cpp @@ -0,0 +1,22 @@ +/* Copyright (C) 2023 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#define VMA_IMPLEMENTATION + +#include "VMA.h"