1
0
forked from 0ad/0ad

Adds Vulkan backend.

Comments By: phosit, Stan
Differential Revision: https://code.wildfiregames.com/D4876
This was SVN commit r27412.
This commit is contained in:
Vladislav Belov 2023-01-10 20:22:20 +00:00
parent de697397ba
commit 7c84c23114
56 changed files with 8182 additions and 199 deletions

View File

@ -117,6 +117,7 @@ cursorbackend = "sdl"
; 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.debugmessages = "false"
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 @@ unload = "U" ; Unload garrisoned units when a building/mechanica
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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" }
]
},
{

View File

@ -175,6 +175,11 @@ void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* devic
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 @@ void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* devic
};
float quadTex[] =
{
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, topV,
0.0f, topV,
0.0f, bottomV,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
0.0f, bottomV,
1.0f, bottomV,
1.0f, topV
};
deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);

View File

@ -251,8 +251,11 @@ CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
}};
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;

View File

@ -58,6 +58,8 @@ public:
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 @@ private:
bool m_TerrainTextureDirty = true;
bool m_FinalTextureDirty = true;
double m_LastFinalTextureUpdate = 0.0;
bool m_Flipped = false;
// minimap texture handles
std::unique_ptr<Renderer::Backend::ITexture> m_TerrainTexture;

View File

@ -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 @@ bool CShaderManager::LoadTechnique(CShaderTechniquePtr& tech)
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);

View File

@ -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 @@ void CMiniMap::Draw(CCanvas2D& canvas)
{
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(

View File

@ -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_ALPHA)
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)

View File

@ -38,6 +38,50 @@
#include <string_view>
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 @@ void CPostprocManager::RecreateBuffers()
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 @@ void CPostprocManager::ApplyBlurDownscale2x(
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 @@ void CPostprocManager::ApplyBlurGauss(
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 @@ void CPostprocManager::ApplyBlurGauss(
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 @@ void CPostprocManager::ApplyEffect(
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 @@ void CPostprocManager::CreateMultisampleBuffer()
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 @@ void CPostprocManager::CreateMultisampleBuffer()
// 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,

View File

@ -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 @@ void CRenderingOptions::ReadConfigAndSetupHooks()
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);

View File

@ -567,6 +567,13 @@ void CSceneRenderer::RenderReflections(
// 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 @@ void CSceneRenderer::RenderRefractions(
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 @@ void CSceneRenderer::RenderSilhouettes(
// 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:

View File

@ -460,6 +460,15 @@ void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
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;
}

View File

@ -57,10 +57,7 @@ struct SWavesVertex
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 @@ void WaterManager::Initialize()
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},

View File

@ -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 @@ enum class Format
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

View File

@ -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 @@ public:
enum class Type
{
VERTEX,
INDEX
INDEX,
UPLOAD,
UNIFORM,
};
virtual Type GetType() const = 0;

View File

@ -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 @@ std::unique_ptr<CBuffer> CBuffer::Create(
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<CBuffer> buffer(new CBuffer());
buffer->m_Device = device;
buffer->m_Type = type;

View File

@ -103,6 +103,10 @@ GLenum BufferTypeToGLTarget(const CBuffer::Type type)
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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "Buffer.h"
#include "renderer/backend/vulkan/Device.h"
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
// static
std::unique_ptr<CBuffer> CBuffer::Create(
CDevice* device, const char* name, const Type type, const uint32_t size,
const bool dynamic)
{
std::unique_ptr<CBuffer> 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<char*>(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<int>(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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_BUFFER
#define INCLUDED_RENDERER_BACKEND_VULKAN_BUFFER
#include "renderer/backend/IBuffer.h"
#include "renderer/backend/vulkan/VMA.h"
#include <glad/vulkan.h>
#include <memory>
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<CBuffer> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <array>
#include <numeric>
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<VkDescriptorSetLayoutBinding, NUMBER_OF_BINDINGS_PER_DESCRIPTOR_INDEXING_SET> 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<VkDescriptorBindingFlagsEXT, NUMBER_OF_BINDINGS_PER_DESCRIPTOR_INDEXING_SET> 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<SingleTypePool>& 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<VkDescriptorSetLayoutBinding, 16> 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<int16_t>(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<CTexture::UID>& texturesUID,
const std::vector<CTexture*>& 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<VkDescriptorSet, int16_t>& 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<uint8_t>(texturesUID.size()), elementIndex});
PS::StaticVector<VkDescriptorImageInfo, 16> infos;
PS::StaticVector<VkWriteDescriptorSet, 16> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DESCRIPTORMANAGER
#define INCLUDED_RENDERER_BACKEND_VULKAN_DESCRIPTORMANAGER
#include "renderer/backend/Sampler.h"
#include "renderer/backend/vulkan/Texture.h"
#include <glad/vulkan.h>
#include <limits>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
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<CTexture::UID>& texturesUID,
const std::vector<CTexture*>& 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<VkDescriptorSetLayout>& GetDescriptorSetLayouts() const { return m_DescriptorSetLayouts; }
private:
struct SingleTypePool
{
VkDescriptorSetLayout layout;
VkDescriptorPool pool;
int16_t firstFreeIndex = 0;
std::vector<std::pair<VkDescriptorSet, int16_t>> 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<VkDescriptorSetLayout> 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<int16_t>::max() >= DESCRIPTOR_INDEXING_BINDING_SIZE);
int16_t firstFreeIndex = 0;
std::vector<int16_t> elements;
std::unordered_map<CTexture::UID, int16_t> map;
};
std::array<DescriptorIndexingBindingMap, NUMBER_OF_BINDINGS_PER_DESCRIPTOR_INDEXING_SET>
m_DescriptorIndexingBindings;
std::unordered_map<CTexture::UID, uint32_t> m_TextureToBindingMap;
std::unordered_map<VkDescriptorType, std::vector<SingleTypePool>> m_SingleTypePools;
std::unordered_map<CTexture::UID, std::vector<std::tuple<VkDescriptorType, uint8_t, int16_t>>> m_TextureSingleTypePoolMap;
using SingleTypeCacheKey = std::pair<VkDescriptorSetLayout, std::vector<CTexture::UID>>;
struct SingleTypeCacheKeyHash
{
size_t operator()(const SingleTypeCacheKey& key) const;
};
std::unordered_map<SingleTypeCacheKey, VkDescriptorSet, SingleTypeCacheKeyHash> m_SingleTypeSets;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_DESCRIPTORMANAGER

View File

@ -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 <algorithm>
#include <iterator>
#include <limits>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
// 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 <SDL_vulkan.h>
#endif
@ -38,59 +68,620 @@ namespace Backend
namespace Vulkan
{
// static
std::unique_ptr<CDevice> CDevice::Create(SDL_Window* UNUSED(window))
namespace
{
std::vector<const char*> 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<const char*> requiredExtensions;
requiredExtensions.reserve(SDLExtensionCount);
std::copy_n(SDLExtensions, SDLExtensionCount, std::back_inserter(requiredExtensions));
return requiredExtensions;
}
std::vector<std::string> GetAvailableValidationLayers()
{
uint32_t layerCount = 0;
ENSURE_VK_SUCCESS(vkEnumerateInstanceLayerProperties(&layerCount, nullptr));
std::vector<VkLayerProperties> 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<std::string> availableValidationLayers;
availableValidationLayers.reserve(layerCount);
for (const VkLayerProperties& layer : availableLayers)
availableValidationLayers.emplace_back(layer.layerName);
return availableValidationLayers;
}
std::vector<std::string> GetAvailableInstanceExtensions(const char* layerName = nullptr)
{
uint32_t extensionCount = 0;
ENSURE_VK_SUCCESS(vkEnumerateInstanceExtensionProperties(layerName, &extensionCount, nullptr));
std::vector<VkExtensionProperties> extensions(extensionCount);
ENSURE_VK_SUCCESS(vkEnumerateInstanceExtensionProperties(layerName, &extensionCount, extensions.data()));
std::vector<std::string> 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<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
return reinterpret_cast<GLADapiproc>(function(instance, name));
#else
return nullptr;
#endif
}
} // anonymous namespace
// static
std::unique_ptr<CDevice> CDevice::Create(SDL_Window* window)
{
if (!window)
{
LOGERROR("Can't create Vulkan device without window.");
return nullptr;
}
GLADuserptrloadfunc gladLoadFunction = reinterpret_cast<GLADuserptrloadfunc>(GetInstanceProcAddr);
std::unique_ptr<CDevice> 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<const char*> 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<const char*> 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<int>(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<const char*> requiredDeviceExtensions =
{
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
std::vector<SAvailablePhysicalDevice> 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<uint32_t>(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<uint32_t>(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<size_t>(heap.size / 1024), static_cast<uint32_t>(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<uint32_t>(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<const char*> deviceExtensions = requiredDeviceExtensions;
if (hasDescriptorIndexing)
deviceExtensions.emplace_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
device->m_GraphicsQueueFamilyIndex = choosenDevice.graphicsQueueFamilyIndex;
const std::array<size_t, 1> queueFamilyIndices{{
choosenDevice.graphicsQueueFamilyIndex
}};
PS::StaticVector<VkDeviceQueueCreateInfo, 1> 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<int>(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<int>(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<VkSampleCountFlagBits, 5> 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<CRenderPassManager>(device.get());
device->m_SamplerManager = std::make_unique<CSamplerManager>(device.get());
device->m_SubmitScheduler =
std::make_unique<CSubmitScheduler>(
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<CDescriptorManager>(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
{
vulkanSupport = "cantload";
}
#endif
Script::SetProperty(rq, settings, "status", vulkanSupport);
}
Script::SetProperty(rq, settings, "extensions", m_Extensions);
std::unique_ptr<IDeviceCommandContext> CDevice::CreateCommandContext()
{
return nullptr;
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)
{
JS::RootedValue device(rq.cx);
Script::CreateObject(rq, &device);
ReportAvailablePhysicalDevice(m_AvailablePhysicalDevices[index], rq, device);
Script::SetPropertyInt(rq, availableDevices, index, device);
}
Script::SetProperty(rq, settings, "available_device", availableDevices);
Script::SetProperty(rq, settings, "instance_extensions", m_InstanceExtensions);
Script::SetProperty(rq, settings, "validation_layers", m_ValidationLayers);
}
std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineStateDesc)
{
UNUSED2(pipelineStateDesc);
return nullptr;
return CGraphicsPipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IVertexInputLayout> CDevice::CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> attributes)
{
UNUSED2(attributes);
return nullptr;
return std::make_unique<CVertexInputLayout>(this, attributes);
}
std::unique_ptr<ITexture> CDevice::CreateTexture(
@ -98,16 +689,9 @@ std::unique_ptr<ITexture> CDevice::CreateTexture(
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<ITexture> CDevice::CreateTexture2D(
@ -124,33 +708,46 @@ std::unique_ptr<IFramebuffer> CDevice::CreateFramebuffer(
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<IBuffer> 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<CBuffer> 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<IShaderProgram> CDevice::CreateShaderProgram(
const CStr& name, const CShaderDefines& defines)
{
UNUSED2(name);
UNUSED2(defines);
return nullptr;
return CShaderProgram::Create(this, name, defines);
}
std::unique_ptr<IDeviceCommandContext> 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 @@ IFramebuffer* CDevice::GetCurrentBackbuffer(
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 @@ void CDevice::OnWindowResize(const uint32_t width, const uint32_t height)
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
{
UNUSED2(usage);
UNUSED2(depth);
UNUSED2(stencil);
return Format::UNDEFINED;
// 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)
{
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<CRingCommandContext> CDevice::CreateRingCommandContext(const size_t size)
{
return std::make_unique<CRingCommandContext>(
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<void*>(object.handle);
#else
const uint64_t handle = object.handle;
#endif
switch (object.type)
{
case VK_OBJECT_TYPE_IMAGE:
vmaDestroyImage(GetVMAAllocator(), static_cast<VkImage>(handle), object.allocation);
break;
case VK_OBJECT_TYPE_BUFFER:
vmaDestroyBuffer(GetVMAAllocator(), static_cast<VkBuffer>(handle), object.allocation);
break;
case VK_OBJECT_TYPE_IMAGE_VIEW:
vkDestroyImageView(m_Device, static_cast<VkImageView>(handle), nullptr);
break;
case VK_OBJECT_TYPE_BUFFER_VIEW:
vkDestroyBufferView(m_Device, static_cast<VkBufferView>(handle), nullptr);
break;
case VK_OBJECT_TYPE_FRAMEBUFFER:
vkDestroyFramebuffer(m_Device, static_cast<VkFramebuffer>(handle), nullptr);
break;
case VK_OBJECT_TYPE_RENDER_PASS:
vkDestroyRenderPass(m_Device, static_cast<VkRenderPass>(handle), nullptr);
break;
case VK_OBJECT_TYPE_SAMPLER:
vkDestroySampler(m_Device, static_cast<VkSampler>(handle), nullptr);
break;
case VK_OBJECT_TYPE_SHADER_MODULE:
vkDestroyShaderModule(m_Device, static_cast<VkShaderModule>(handle), nullptr);
break;
case VK_OBJECT_TYPE_PIPELINE_LAYOUT:
vkDestroyPipelineLayout(m_Device, static_cast<VkPipelineLayout>(handle), nullptr);
break;
case VK_OBJECT_TYPE_PIPELINE:
vkDestroyPipeline(m_Device, static_cast<VkPipeline>(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<IDevice> CreateDevice(SDL_Window* window)

View File

@ -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 <glad/vulkan.h>
#include <memory>
#include <limits>
#include <queue>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
typedef struct SDL_Window SDL_Window;
@ -35,7 +45,18 @@ namespace Backend
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 @@ public:
std::unique_ptr<IBuffer> CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override;
std::unique_ptr<CBuffer> CreateCBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic);
std::unique_ptr<IShaderProgram> CreateShaderProgram(
const CStr& name, const CShaderDefines& defines) override;
@ -103,15 +127,88 @@ public:
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<uint64_t>(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<uint64_t>(handle), name);
}
void SetObjectName(VkObjectType type, const uint64_t handle, const char* name);
std::unique_ptr<CRingCommandContext> 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<std::string> m_Extensions;
std::vector<std::string> m_InstanceExtensions;
std::vector<std::string> m_ValidationLayers;
SAvailablePhysicalDevice m_ChoosenDevice{};
std::vector<SAvailablePhysicalDevice> 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<uint32_t>::max();
std::unique_ptr<CSwapChain> m_SwapChain;
uint32_t m_FrameID = 0;
struct ObjectToDestroy
{
uint32_t frameID;
VkObjectType type;
uint64_t handle;
VmaAllocation allocation;
};
std::queue<ObjectToDestroy> m_ObjectToDestroyQueue;
std::queue<std::pair<uint32_t, CTexture::UID>> m_TextureToDestroyQueue;
std::unique_ptr<CRenderPassManager> m_RenderPassManager;
std::unique_ptr<CSamplerManager> m_SamplerManager;
std::unique_ptr<CDescriptorManager> m_DescriptorManager;
std::unique_ptr<CSubmitScheduler> m_SubmitScheduler;
};
} // namespace Vulkan

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
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<CTexture* const> 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<CTexture* const> 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<IDeviceCommandContext> CDeviceCommandContext::Create(CDevice* device)
{
std::unique_ptr<CDeviceCommandContext> 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<CGraphicsPipelineState>();
CShaderProgram* shaderProgram = m_GraphicsPipelineState->GetShaderProgram()->As<CShaderProgram>();
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<CFramebuffer>()->GetColorAttachments();
const auto& destinationColorAttachments =
destinationFramebuffer->As<CFramebuffer>()->GetColorAttachments();
ENSURE(sourceColorAttachments.size() == destinationColorAttachments.size());
// TODO: account depth.
//ENSURE(
// static_cast<bool>(sourceFramebuffer->As<CFramebuffer>()->GetDepthStencilAttachment()) ==
// static_cast<bool>(destinationFramebuffer->As<CFramebuffer>()->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<CFramebuffer>()->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, &region, VK_FILTER_NEAREST);
}
}
else
{
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetSampleCount() > 1);
ENSURE(destinationFramebuffer->As<CFramebuffer>()->GetSampleCount() == 1);
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetWidth() == destinationFramebuffer->As<CFramebuffer>()->GetWidth());
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetHeight() == destinationFramebuffer->As<CFramebuffer>()->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, &region);
}
}
}
void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil)
{
ENSURE(m_InsideFramebufferPass);
ENSURE(m_Framebuffer);
PS::StaticVector<VkClearAttachment, 4> 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<CFramebuffer>();
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<VkClearValue, 4> 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<CTexture>(), 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<CTexture>(), 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<CBuffer>(), data, 0, dataSize);
}
void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction)
{
ENSURE(!m_InsideFramebufferPass);
m_CommandContext->ScheduleUpload(
buffer->As<CBuffer>(), 0, buffer->As<CBuffer>()->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<CBuffer>(), data, dataOffset, dataSize);
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction)
{
m_CommandContext->ScheduleUpload(
buffer->As<CBuffer>(), 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<float>(viewports[0].x);
viewport.y = static_cast<float>(static_cast<int32_t>(m_Framebuffer->GetHeight()) - viewports[0].y - viewports[0].height);
viewport.width = static_cast<float>(viewports[0].width);
viewport.height = static_cast<float>(viewports[0].height);
vkCmdSetViewport(m_CommandContext->GetCommandBuffer(), 0, 1, &viewport);
}
void CDeviceCommandContext::SetVertexInputLayout(
IVertexInputLayout* vertexInputLayout)
{
ENSURE(vertexInputLayout);
m_IsPipelineStateDirty = true;
m_VertexInputLayout = vertexInputLayout->As<CVertexInputLayout>();
}
void CDeviceCommandContext::SetVertexBuffer(
const uint32_t bindingSlot, IBuffer* buffer, const uint32_t offset)
{
BindVertexBuffer(bindingSlot, buffer->As<CBuffer>(), 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<uint8_t*>(m_InPlaceVertexStagingBufferMappedData) + destination, data, dataSize);
BindVertexBuffer(bindingSlot, m_InPlaceVertexBuffer.get(), destination2);
}
void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer)
{
BindIndexBuffer(buffer->As<CBuffer>(), 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<uint8_t*>(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<CTexture>();
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<const float> 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, &region);
}
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, &region);
}
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, &region);
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<VkDeviceSize>(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<uint8_t*>(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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT
#define INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT
#include "renderer/backend/IBuffer.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include <glad/vulkan.h>
#include <memory>
#include <vector>
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<const float> values) override;
void BeginScopedLabel(const char* name) override;
void EndScopedLabel() override;
void Flush() override;
private:
friend class CDevice;
static std::unique_ptr<IDeviceCommandContext> 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<CRingCommandContext> m_PrependCommandContext;
std::unique_ptr<CRingCommandContext> 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<CBuffer> m_UniformBuffer;
std::unique_ptr<CBuffer> 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<CBuffer> m_InPlaceVertexBuffer;
std::unique_ptr<CBuffer> m_InPlaceIndexBuffer;
std::unique_ptr<CBuffer> m_InPlaceVertexStagingBuffer;
std::unique_ptr<CBuffer> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
#include <limits>
#include <string>
#include <type_traits>
#include <vector>
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
namespace
{
std::vector<std::string> GetPhysicalDeviceExtensions(VkPhysicalDevice device)
{
uint32_t extensionCount = 0;
ENSURE_VK_SUCCESS(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr));
std::vector<VkExtensionProperties> extensions(extensionCount);
ENSURE_VK_SUCCESS(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, extensions.data()));
std::vector<std::string> 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<typename T, typename Tag = void>
struct ReportFormatHelper
{
std::string operator()(const T&) const { return "unknown"; }
};
template<typename T>
struct ReportFormatHelper<T, typename std::enable_if_t<std::is_floating_point_v<T>>>
{
float operator()(const T& value) const { return static_cast<float>(value); }
};
template<typename T>
struct ReportFormatHelper<T, typename std::enable_if_t<std::is_integral_v<T>>>
{
static constexpr bool IsSigned = std::is_signed_v<T>;
using ResultType = std::conditional_t<IsSigned, int32_t, uint32_t>;
uint32_t operator()(const T& value) const
{
if (value > std::numeric_limits<ResultType>::max())
return std::numeric_limits<ResultType>::max();
if constexpr (IsSigned)
{
if (value < std::numeric_limits<ResultType>::min())
return std::numeric_limits<ResultType>::min();
}
return static_cast<ResultType>(value);
}
};
template<typename T>
struct ReportFormatHelper<T, typename std::enable_if_t<std::is_enum_v<T>>>
{
using HelperType = ReportFormatHelper<std::underlying_type_t<T>>;
using ResultType = std::invoke_result_t<HelperType, std::underlying_type_t<T>>;
ResultType operator()(const T& value) const
{
HelperType helper{};
return helper(value);
}
};
template<typename T>
struct ReportFormatHelper<T, typename std::enable_if_t<std::is_array_v<T>>>
{
using HelperType = ReportFormatHelper<std::remove_extent_t<T>>;
using ElementType = std::invoke_result_t<HelperType, std::remove_extent_t<T>>;
std::vector<ElementType> operator()(const T& value) const
{
std::vector<ElementType> 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<const char*>& 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<SAvailablePhysicalDevice> GetAvailablePhysicalDevices(
VkInstance instance, VkSurfaceKHR surface,
const std::vector<const char*>& requiredDeviceExtensions)
{
uint32_t physicalDeviceCount = 0;
ENSURE_VK_SUCCESS(vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr));
if (physicalDeviceCount == 0)
return {};
std::vector<SAvailablePhysicalDevice> availablePhysicalDevices;
availablePhysicalDevices.reserve(physicalDeviceCount);
std::vector<VkPhysicalDevice> 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<int32_t>(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<uint32_t>(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<uint32_t>(heap.size / 1024));
Script::SetProperty(rq, memoryHeap, "flags", static_cast<uint32_t>(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<decltype(device.properties.limits.NAME)> 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<decltype(device.descriptorIndexingProperties.NAME)> 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<bool>(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<bool>(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<uint32_t>(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<uint32_t>(device.surfaceFormats[index].format));
Script::SetProperty(
rq, surfaceFormat, "color_space", static_cast<uint32_t>(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<decltype(device.surfaceCapabilities.NAME)> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DEVICESELECTION
#define INCLUDED_RENDERER_BACKEND_VULKAN_DEVICESELECTION
#include "scriptinterface/ScriptForward.h"
#include <glad/vulkan.h>
#include <limits>
#include <vector>
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<uint32_t>::max();
VkPhysicalDevice device = VK_NULL_HANDLE;
VkPhysicalDeviceProperties properties{};
VkPhysicalDeviceDescriptorIndexingPropertiesEXT descriptorIndexingProperties{};
VkPhysicalDeviceMemoryProperties memoryProperties{};
VkPhysicalDeviceFeatures features{};
VkPhysicalDeviceDescriptorIndexingFeaturesEXT descriptorIndexingFeatures{};
std::vector<VkQueueFamilyProperties> queueFamilies;
bool hasRequiredExtensions = false;
bool hasOutputToSurfaceSupport = false;
size_t graphicsQueueFamilyIndex = 0;
size_t presentQueueFamilyIndex = 0;
VkDeviceSize deviceTotalMemory = 0;
VkDeviceSize hostTotalMemory = 0;
std::vector<std::string> extensions;
VkSurfaceCapabilitiesKHR surfaceCapabilities;
std::vector<VkSurfaceFormatKHR> surfaceFormats;
std::vector<VkPresentModeKHR> 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<SAvailablePhysicalDevice> GetAvailablePhysicalDevices(
VkInstance instance, VkSurfaceKHR surface,
const std::vector<const char*>& 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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> CFramebuffer::Create(
CDevice* device, const char* name,
SColorAttachment* colorAttachment, SDepthStencilAttachment* depthStencilAttachment)
{
ENSURE(colorAttachment || depthStencilAttachment);
if (colorAttachment && depthStencilAttachment)
{
CTexture* colorAttachmentTexture = colorAttachment->texture->As<CTexture>();
CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As<CTexture>();
ENSURE(
colorAttachmentTexture->GetWidth() == depthStencilAttachmentTexture->GetWidth() &&
colorAttachmentTexture->GetHeight() == depthStencilAttachmentTexture->GetHeight() &&
colorAttachmentTexture->GetSampleCount() == depthStencilAttachmentTexture->GetSampleCount());
}
std::unique_ptr<CFramebuffer> framebuffer(new CFramebuffer());
framebuffer->m_Device = device;
if (colorAttachment)
framebuffer->m_ClearColor = colorAttachment->clearColor;
PS::StaticVector<VkImageView, 4> attachments;
if (colorAttachment)
{
CTexture* colorAttachmentTexture = colorAttachment->texture->As<CTexture>();
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<CTexture>();
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_FRAMEBUFFER
#define INCLUDED_RENDERER_BACKEND_VULKAN_FRAMEBUFFER
#include "ps/containers/StaticVector.h"
#include "renderer/backend/IFramebuffer.h"
#include <glad/vulkan.h>
#include <memory>
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<CTexture*, 4>& 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<CFramebuffer> 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<CTexture*, 4> m_ColorAttachments;
CTexture* m_DepthStencilAttachment = nullptr;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_FRAMEBUFFER

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <glad/vulkan.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
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> CGraphicsPipelineState::Create(
CDevice* device, const SGraphicsPipelineStateDesc& desc)
{
ENSURE(desc.shaderProgram);
std::unique_ptr<CGraphicsPipelineState> 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<CShaderProgram>();
const CacheKey cacheKey =
{
vertexInputLayout->GetUID(), framebuffer->GetUID()
};
auto it = m_PipelineMap.find(cacheKey);
if (it != m_PipelineMap.end())
return it->second;
PS::StaticVector<VkVertexInputBindingDescription, 16> attributeBindings;
PS::StaticVector<VkVertexInputAttributeDescription, 16> 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<uint32_t>::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<uint32_t>(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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <cstdint>
#include <glad/vulkan.h>
#include <memory>
#include <unordered_map>
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<CGraphicsPipelineState> 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<CacheKey, VkPipeline, CacheKeyHash, CacheKeyEqual> m_PipelineMap;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_PIPELINESTATE

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<Attachment>& lhs, const std::optional<Attachment>& 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<CTexture>();
desc.sampleCount = colorAttachmentTexture->GetSampleCount();
desc.colorAttachment.emplace(Attachment{
colorAttachmentTexture->GetVkFormat(),
colorAttachment->loadOp,
colorAttachment->storeOp});
}
if (depthStencilAttachment)
{
CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As<CTexture>();
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<VkAttachmentDescription, 4> attachmentDescs;
std::optional<VkAttachmentReference> colorAttachmentRef;
std::optional<VkAttachmentReference> depthStencilAttachmentRef;
if (colorAttachment)
{
CTexture* colorAttachmentTexture = colorAttachment->texture->As<CTexture>();
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<CTexture>();
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_RENDERPASSMANAGER
#define INCLUDED_RENDERER_BACKEND_VULKAN_RENDERPASSMANAGER
#include "renderer/backend/IFramebuffer.h"
#include <glad/vulkan.h>
#include <optional>
#include <unordered_map>
#include <vector>
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<Attachment> colorAttachment;
std::optional<Attachment> depthStencilAttachment;
};
struct DescHash
{
size_t operator()(const Desc& desc) const;
};
struct DescEqual
{
bool operator()(const Desc& lhs, const Desc& rhs) const;
};
std::unordered_map<Desc, VkRenderPass, DescHash, DescEqual> m_RenderPassMap;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_RENDERPASSMANAGER

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
#include <cstddef>
#include <limits>
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<uint32_t>::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<uint32_t>(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<std::byte*>(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<int32_t>(xOffset), static_cast<int32_t>(yOffset), 0};
region.imageExtent = {width, height, 1};
vkCmdCopyBufferToImage(
commandBuffer, m_StagingBuffer->GetVkBuffer(), image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
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<std::byte*>(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<CBuffer>();
uploadFunction(static_cast<uint8_t*>(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<CBuffer>();
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, &region);
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_RINGCOMMANDCONTEXT
#define INCLUDED_RENDERER_BACKEND_VULKAN_RINGCOMMANDCONTEXT
#include "renderer/backend/vulkan/SubmitScheduler.h"
#include <glad/vulkan.h>
#include <memory>
#include <vector>
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(u8*)>;
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<CBuffer> 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<RingItem> m_Ring;
size_t m_RingIndex = 0;
void WaitUntilFree(RingItem& item);
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_RINGCOMMANDCONTEXT

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SAMPLERMANAGER
#define INCLUDED_RENDERER_BACKEND_VULKAN_SAMPLERMANAGER
#include "renderer/backend/Sampler.h"
#include <glad/vulkan.h>
#include <memory>
#include <unordered_map>
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<Sampler::Desc, VkSampler, SamplerDescHash, SamplerDescEqual> m_SamplerMap;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SAMPLERMANAGER

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
#include <limits>
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<uintptr_t>(file.GetBuffer()) % alignof(uint32_t) == 0u);
createInfo.codeSize = file.GetBufferSize();
createInfo.pCode = reinterpret_cast<const uint32_t*>(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> CShaderProgram::Create(
CDevice* device, const CStr& name, const CShaderDefines& baseDefines)
{
const VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml";
std::unique_ptr<CShaderProgram> 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<VkPushConstantRange> 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<std::byte[]>(shaderProgram->m_MaterialConstantsDataSize);
}
std::vector<VkDescriptorSetLayout> 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<int>(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<VfsPath> 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<uint32_t>::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<const float>(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<const float>(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<const float>(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<const float>(values, values + 4));
}
void CShaderProgram::SetUniform(const int32_t bindingSlot, PS::span<const float> values)
{
if (bindingSlot < 0)
return;
const auto data = GetUniformData(bindingSlot, values.size() * sizeof(float));
std::memcpy(data.first, values.data(), data.second);
}
std::pair<std::byte*, uint32_t> CShaderProgram::GetUniformData(
const int32_t bindingSlot, const uint32_t dataSize)
{
if (bindingSlot < static_cast<int32_t>(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<CTexture>());
ENSURE(bindingSlot < static_cast<int32_t>(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<int32_t>(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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM
#define INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM
#include "renderer/backend/IShaderProgram.h"
#include "renderer/backend/vulkan/Texture.h"
#include <array>
#include <cstddef>
#include <glad/vulkan.h>
#include <memory>
#include <unordered_map>
#include <vector>
class CShaderDefines;
class CStr;
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
class CDevice;
class CVertexInputLayout : public IVertexInputLayout
{
public:
CVertexInputLayout(CDevice* device, const PS::span<const SVertexAttributeFormat> 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<SVertexAttributeFormat>& 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<SVertexAttributeFormat> m_Attributes;
};
class CShaderProgram final : public IShaderProgram
{
public:
~CShaderProgram() override;
IDevice* GetDevice() override;
int32_t GetBindingSlot(const CStrIntern name) const override;
std::vector<VfsPath> GetFileDependencies() const override;
uint32_t GetStreamLocation(const VertexAttributeStream stream) const;
const std::vector<VkPipelineShaderStageCreateInfo>& 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<const float> 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<std::byte*, uint32_t> GetUniformData(
const int32_t bindingSlot, const uint32_t dataSize);
static std::unique_ptr<CShaderProgram> Create(
CDevice* device, const CStr& name, const CShaderDefines& defines);
void UpdateActiveDescriptorSet(
VkCommandBuffer commandBuffer);
CDevice* m_Device = nullptr;
std::vector<VkShaderModule> m_ShaderModules;
std::vector<VkPipelineShaderStageCreateInfo> m_Stages;
VkPipelineLayout m_PipelineLayout = VK_NULL_HANDLE;
std::vector<VfsPath> 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<std::byte[]> m_MaterialConstantsData;
uint32_t m_MaterialConstantsDataSize = 0;
bool m_MaterialConstantsDataOutdated = false;
std::array<std::byte, 128> m_PushConstantData;
uint32_t m_PushConstantDataMask = 0;
std::array<VkShaderStageFlags, 32> m_PushConstantDataFlags;
std::vector<PushConstant> m_PushConstants;
std::vector<Uniform> m_Uniforms;
std::unordered_map<CStrIntern, uint32_t> m_UniformMapping;
std::unordered_map<CStrIntern, uint32_t> m_PushConstantMapping;
uint32_t m_TexturesDescriptorSetSize = 0;
bool m_BoundTexturesOutdated = false;
VkDescriptorSetLayout m_TexturesDescriptorSetLayout = VK_NULL_HANDLE;
std::vector<CTexture*> m_BoundTextures;
std::vector<CTexture::UID> m_BoundTexturesUID;
VkDescriptorSet m_ActiveTexturesDescriptorSet = VK_NULL_HANDLE;
std::unordered_map<CStrIntern, uint32_t> m_TextureMapping;
std::unordered_map<VertexAttributeStream, uint32_t> m_StreamLocations;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CRingCommandContext>(
device, NUMBER_OF_FRAMES_IN_FLIGHT, queueFamilyIndex, *this);
m_PresentCommandContext = std::make_unique<CRingCommandContext>(
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<uint64_t>::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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SUBMITSCHEDULER
#define INCLUDED_RENDERER_BACKEND_VULKAN_SUBMITSCHEDULER
#include "renderer/backend/vulkan/Device.h"
#include <glad/vulkan.h>
#include <memory>
#include <queue>
#include <utility>
#include <vector>
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<Fence> 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<SubmittedHandle> 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<FrameObject, NUMBER_OF_FRAMES_IN_FLIGHT> m_FrameObjects;
VkSemaphore m_NextWaitSemaphore = VK_NULL_HANDLE;
VkPipelineStageFlags m_NextWaitDstStageMask = 0;
VkSemaphore m_NextSubmitSignalSemaphore = VK_NULL_HANDLE;
std::vector<VkCommandBuffer> m_SubmittedCommandBuffers;
std::unique_ptr<CRingCommandContext> m_AcquireCommandContext;
std::unique_ptr<CRingCommandContext> m_PresentCommandContext;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SUBMITSCHEDULER

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
#include <limits>
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
// static
std::unique_ptr<CSwapChain> CSwapChain::Create(
CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight,
std::unique_ptr<CSwapChain> oldSwapChain)
{
std::unique_ptr<CSwapChain> swapChain(new CSwapChain());
swapChain->m_Device = device;
VkPhysicalDevice physicalDevice = device->GetChoosenPhysicalDevice().device;
VkSurfaceCapabilitiesKHR surfaceCapabilities{};
ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
physicalDevice, surface, &surfaceCapabilities));
std::vector<VkSurfaceFormatKHR> 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<VkPresentModeKHR> 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<int>(surfaceDrawableWidth,
surfaceCapabilities.minImageExtent.width,
surfaceCapabilities.maxImageExtent.width);
const uint32_t swapChainHeight = Clamp<int>(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<uint32_t>(NUMBER_OF_FRAMES_IN_FLIGHT,
surfaceCapabilities.minImageCount + 1,
surfaceCapabilities.maxImageCount > 0
? surfaceCapabilities.maxImageCount
: std::numeric_limits<uint32_t>::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<uint32_t>::max());
const VkResult acquireResult = vkAcquireNextImageKHR(
m_Device->GetVkDevice(), m_SwapChain, std::numeric_limits<uint64_t>::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<int>(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<uint32_t>::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<uint32_t>::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<int>(presentResult));
debug_warn("Unknown present error.");
}
}
m_CurrentImageIndex = std::numeric_limits<uint32_t>::max();
}
CFramebuffer* CSwapChain::GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp)
{
ENSURE(m_CurrentImageIndex != std::numeric_limits<uint32_t>::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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN
#define INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN
#include "renderer/backend/IFramebuffer.h"
#include <glad/vulkan.h>
#include <memory>
#include <tuple>
#include <unordered_map>
#include <vector>
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<CSwapChain> Create(
CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight,
std::unique_ptr<CSwapChain> oldSwapChain);
CSwapChain();
CDevice* m_Device = nullptr;
bool m_IsValid = false;
VkSwapchainKHR m_SwapChain = VK_NULL_HANDLE;
uint32_t m_CurrentImageIndex = std::numeric_limits<uint32_t>::max();
std::vector<VkImage> m_Images;
std::vector<std::unique_ptr<CTexture>> m_Textures;
std::unique_ptr<CTexture> 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<CFramebuffer>, BackbufferKeyHash> backbuffers;
SwapChainBackbuffer();
SwapChainBackbuffer(const SwapChainBackbuffer&) = delete;
SwapChainBackbuffer& operator=(const SwapChainBackbuffer&) = delete;
SwapChainBackbuffer(SwapChainBackbuffer&& other);
SwapChainBackbuffer& operator=(SwapChainBackbuffer&& other);
};
std::vector<SwapChainBackbuffer> m_Backbuffers;
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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> 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<CTexture> 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<int>(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<int>(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<int>(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<char*>(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<int>(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> 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<CTexture> 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <glad/vulkan.h>
#include <memory>
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<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);
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);
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_UTILITIES
#define INCLUDED_RENDERER_BACKEND_VULKAN_UTILITIES
#include "ps/CStr.h"
#include <glad/vulkan.h>
#define ENSURE_VK_SUCCESS(EXPR) \
do \
{ \
const VkResult result = (EXPR); \
if (result != VK_SUCCESS) \
{ \
LOGERROR(#EXPR " returned %d instead of VK_SUCCESS", static_cast<int>(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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#define VMA_IMPLEMENTATION
#include "VMA.h"

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <glad/vulkan.h>
#include <mutex>
#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