Adds Vulkan backend.
Comments By: phosit, Stan Differential Revision: https://code.wildfiregames.com/D4876 This was SVN commit r27412.
This commit is contained in:
parent
de697397ba
commit
7c84c23114
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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},
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
117
source/renderer/backend/vulkan/Buffer.cpp
Normal file
117
source/renderer/backend/vulkan/Buffer.cpp
Normal 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
|
82
source/renderer/backend/vulkan/Buffer.h
Normal file
82
source/renderer/backend/vulkan/Buffer.h
Normal 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
|
365
source/renderer/backend/vulkan/DescriptorManager.cpp
Normal file
365
source/renderer/backend/vulkan/DescriptorManager.cpp
Normal 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
|
124
source/renderer/backend/vulkan/DescriptorManager.h
Normal file
124
source/renderer/backend/vulkan/DescriptorManager.h
Normal 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
962
source/renderer/backend/vulkan/DeviceCommandContext.cpp
Normal file
962
source/renderer/backend/vulkan/DeviceCommandContext.cpp
Normal 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, ®ion, 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, ®ion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, ®ion);
|
||||
}
|
||||
|
||||
if (m_InPlaceBlockIndexOffset > 0)
|
||||
{
|
||||
VkBufferCopy region{};
|
||||
region.srcOffset = m_InPlaceBlockIndex * FRAME_INPLACE_BUFFER_SIZE;
|
||||
region.dstOffset = 0;
|
||||
region.size = m_InPlaceBlockIndexOffset;
|
||||
vkCmdCopyBuffer(
|
||||
m_PrependCommandContext->GetCommandBuffer(),
|
||||
m_InPlaceIndexStagingBuffer->GetVkBuffer(),
|
||||
m_InPlaceIndexBuffer->GetVkBuffer(), 1, ®ion);
|
||||
}
|
||||
|
||||
if (m_InPlaceBlockVertexOffset > 0 || m_InPlaceBlockIndexOffset > 0)
|
||||
{
|
||||
VkMemoryBarrier memoryBarrier{};
|
||||
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||
memoryBarrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
memoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
m_PrependCommandContext->GetCommandBuffer(),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0,
|
||||
1, &memoryBarrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
if (m_UniformOffset > 0)
|
||||
{
|
||||
VkBufferCopy region{};
|
||||
// TODO: fix values
|
||||
region.srcOffset = (m_UniformStagingBuffer->GetSize() / NUMBER_OF_FRAMES_IN_FLIGHT) * m_UniformIndexOffset;
|
||||
region.dstOffset = 0;
|
||||
region.size = m_UniformOffset;
|
||||
vkCmdCopyBuffer(
|
||||
m_PrependCommandContext->GetCommandBuffer(),
|
||||
m_UniformStagingBuffer->GetVkBuffer(),
|
||||
m_UniformBuffer->GetVkBuffer(), 1, ®ion);
|
||||
m_UniformIndexOffset = (m_UniformIndexOffset + 1) % NUMBER_OF_FRAMES_IN_FLIGHT;
|
||||
m_UniformOffset = 0;
|
||||
}
|
||||
|
||||
m_IsPipelineStateDirty = true;
|
||||
// TODO: maybe move management to CDevice.
|
||||
m_InPlaceBlockIndex = (m_InPlaceBlockIndex + 1) % NUMBER_OF_FRAMES_IN_FLIGHT;
|
||||
m_InPlaceBlockVertexOffset = 0;
|
||||
m_InPlaceBlockIndexOffset = 0;
|
||||
|
||||
m_PrependCommandContext->Flush();
|
||||
m_CommandContext->Flush();
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::PreDraw()
|
||||
{
|
||||
ENSURE(m_InsidePass);
|
||||
ApplyPipelineStateIfDirty();
|
||||
m_ShaderProgram->PreDraw(m_CommandContext->GetCommandBuffer());
|
||||
if (m_ShaderProgram->IsMaterialConstantsDataOutdated())
|
||||
{
|
||||
const VkDeviceSize alignment =
|
||||
std::max(static_cast<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
|
196
source/renderer/backend/vulkan/DeviceCommandContext.h
Normal file
196
source/renderer/backend/vulkan/DeviceCommandContext.h
Normal 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
|
548
source/renderer/backend/vulkan/DeviceSelection.cpp
Normal file
548
source/renderer/backend/vulkan/DeviceSelection.cpp
Normal 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
|
104
source/renderer/backend/vulkan/DeviceSelection.h
Normal file
104
source/renderer/backend/vulkan/DeviceSelection.h
Normal 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
|
129
source/renderer/backend/vulkan/Framebuffer.cpp
Normal file
129
source/renderer/backend/vulkan/Framebuffer.cpp
Normal 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
|
110
source/renderer/backend/vulkan/Framebuffer.h
Normal file
110
source/renderer/backend/vulkan/Framebuffer.h
Normal 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
|
280
source/renderer/backend/vulkan/Mapping.cpp
Normal file
280
source/renderer/backend/vulkan/Mapping.cpp
Normal 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
|
72
source/renderer/backend/vulkan/Mapping.h
Normal file
72
source/renderer/backend/vulkan/Mapping.h
Normal 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
|
282
source/renderer/backend/vulkan/PipelineState.cpp
Normal file
282
source/renderer/backend/vulkan/PipelineState.cpp
Normal 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
|
100
source/renderer/backend/vulkan/PipelineState.h
Normal file
100
source/renderer/backend/vulkan/PipelineState.h
Normal 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
|
200
source/renderer/backend/vulkan/RenderPassManager.cpp
Normal file
200
source/renderer/backend/vulkan/RenderPassManager.cpp
Normal 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
|
89
source/renderer/backend/vulkan/RenderPassManager.h
Normal file
89
source/renderer/backend/vulkan/RenderPassManager.h
Normal 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
|
431
source/renderer/backend/vulkan/RingCommandContext.cpp
Normal file
431
source/renderer/backend/vulkan/RingCommandContext.cpp
Normal 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, ®ion);
|
||||
|
||||
VkAccessFlags dstAccessFlags = VK_ACCESS_SHADER_READ_BIT;
|
||||
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
Utilities::SubmitImageMemoryBarrier(
|
||||
commandBuffer, image, level, layer,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessFlags,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask);
|
||||
texture->SetInitialized();
|
||||
}
|
||||
|
||||
void CRingCommandContext::ScheduleUpload(
|
||||
CBuffer* buffer, const void* data, const uint32_t dataOffset,
|
||||
const uint32_t dataSize)
|
||||
{
|
||||
constexpr uint32_t alignment = 16;
|
||||
const uint32_t offset = AcquireFreeSpace(dataSize, alignment);
|
||||
|
||||
std::memcpy(static_cast<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, ®ion);
|
||||
|
||||
VkAccessFlags srcAccessFlags = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
VkAccessFlags dstAccessFlags = 0;
|
||||
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
dstStageMask = 0;
|
||||
if (buffer->GetType() == IBuffer::Type::VERTEX)
|
||||
{
|
||||
dstAccessFlags = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
|
||||
dstStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
}
|
||||
else if (buffer->GetType() == IBuffer::Type::INDEX)
|
||||
{
|
||||
dstAccessFlags = VK_ACCESS_INDEX_READ_BIT;
|
||||
dstStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
}
|
||||
else if (buffer->GetType() == IBuffer::Type::UNIFORM)
|
||||
{
|
||||
dstAccessFlags = VK_ACCESS_UNIFORM_READ_BIT;
|
||||
dstStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
}
|
||||
Utilities::SubmitBufferMemoryBarrier(
|
||||
commandBuffer, buffer, dataOffset, dataSize,
|
||||
srcAccessFlags, dstAccessFlags, srcStageMask, dstStageMask);
|
||||
}
|
||||
|
||||
void CRingCommandContext::Begin()
|
||||
{
|
||||
RingItem& item = m_Ring[m_RingIndex];
|
||||
item.isBegan = true;
|
||||
|
||||
WaitUntilFree(item);
|
||||
|
||||
m_StagingBufferCurrentFirst = m_StagingBufferLast;
|
||||
|
||||
ENSURE_VK_SUCCESS(vkResetCommandPool(m_Device->GetVkDevice(), item.commandPool, 0));
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = 0;
|
||||
beginInfo.pInheritanceInfo = nullptr;
|
||||
ENSURE_VK_SUCCESS(vkBeginCommandBuffer(item.commandBuffer, &beginInfo));
|
||||
}
|
||||
|
||||
void CRingCommandContext::End()
|
||||
{
|
||||
RingItem& item = m_Ring[m_RingIndex];
|
||||
item.isBegan = false;
|
||||
item.stagingBufferFirst = m_StagingBufferCurrentFirst;
|
||||
item.stagingBufferLast = m_StagingBufferLast;
|
||||
|
||||
ENSURE_VK_SUCCESS(vkEndCommandBuffer(item.commandBuffer));
|
||||
}
|
||||
|
||||
void CRingCommandContext::WaitUntilFree(RingItem& item)
|
||||
{
|
||||
m_SubmitScheduler.WaitUntilFree(item.handle);
|
||||
if (item.stagingBufferFirst != item.stagingBufferLast)
|
||||
{
|
||||
m_StagingBufferFirst = item.stagingBufferLast;
|
||||
item.stagingBufferFirst = 0;
|
||||
item.stagingBufferLast = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CRingCommandContext::AcquireFreeSpace(
|
||||
const uint32_t requiredSize, const uint32_t requiredAlignment)
|
||||
{
|
||||
ENSURE(requiredSize <= m_MaxStagingBufferCapacity);
|
||||
const uint32_t offsetCandidate =
|
||||
GetFreeSpaceOffset(requiredSize, requiredAlignment);
|
||||
const bool needsResize =
|
||||
!m_StagingBuffer || offsetCandidate == INVALID_OFFSET;
|
||||
const bool canResize =
|
||||
!m_StagingBuffer || m_StagingBuffer->GetSize() < m_MaxStagingBufferCapacity;
|
||||
if (needsResize && canResize)
|
||||
{
|
||||
const uint32_t minimumRequiredCapacity = round_up_to_pow2(requiredSize);
|
||||
const uint32_t newCapacity = std::min(
|
||||
std::max(m_StagingBuffer ? m_StagingBuffer->GetSize() * 2 : INITIAL_STAGING_BUFFER_CAPACITY, minimumRequiredCapacity),
|
||||
m_MaxStagingBufferCapacity);
|
||||
m_StagingBuffer = m_Device->CreateCBuffer(
|
||||
"UploadRingBuffer", IBuffer::Type::UPLOAD, newCapacity, false);
|
||||
ENSURE(m_StagingBuffer);
|
||||
m_StagingBufferFirst = 0;
|
||||
m_StagingBufferCurrentFirst = 0;
|
||||
m_StagingBufferLast = requiredSize;
|
||||
|
||||
for (RingItem& item : m_Ring)
|
||||
{
|
||||
item.stagingBufferFirst = 0;
|
||||
item.stagingBufferLast = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (needsResize)
|
||||
{
|
||||
// In case we can't resize we need to wait until all scheduled uploads are
|
||||
// completed.
|
||||
for (size_t ringIndexOffset = 1; ringIndexOffset < m_Ring.size() && GetFreeSpaceOffset(requiredSize, requiredAlignment) == INVALID_OFFSET; ++ringIndexOffset)
|
||||
{
|
||||
const size_t ringIndex = (m_RingIndex + ringIndexOffset) % m_Ring.size();
|
||||
RingItem& item = m_Ring[ringIndex];
|
||||
WaitUntilFree(item);
|
||||
}
|
||||
// If we still don't have a free space it means we need to flush the
|
||||
// current command buffer.
|
||||
const uint32_t offset = GetFreeSpaceOffset(requiredSize, requiredAlignment);
|
||||
if (offset == INVALID_OFFSET)
|
||||
{
|
||||
RingItem& item = m_Ring[m_RingIndex];
|
||||
if (item.isBegan)
|
||||
Flush();
|
||||
WaitUntilFree(item);
|
||||
m_StagingBufferFirst = 0;
|
||||
m_StagingBufferCurrentFirst = 0;
|
||||
m_StagingBufferLast = requiredSize;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_StagingBufferLast = offset + requiredSize;
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_StagingBufferLast = offsetCandidate + requiredSize;
|
||||
return offsetCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CRingCommandContext::GetFreeSpaceOffset(
|
||||
const uint32_t requiredSize, const uint32_t requiredAlignment) const
|
||||
{
|
||||
if (!m_StagingBuffer)
|
||||
return INVALID_OFFSET;
|
||||
const uint32_t candidateOffset =
|
||||
round_up(m_StagingBufferLast, requiredAlignment);
|
||||
const uint32_t candidateLast = candidateOffset + requiredSize;
|
||||
if (m_StagingBufferFirst <= m_StagingBufferLast)
|
||||
{
|
||||
if (candidateLast <= m_StagingBuffer->GetSize())
|
||||
return candidateOffset;
|
||||
// We intentionally use exclusive comparison to avoid distinguishing
|
||||
// completely full and completely empty staging buffers.
|
||||
else if (requiredSize < m_StagingBufferFirst)
|
||||
return 0; // We assume the first byte is always perfectly aligned.
|
||||
else
|
||||
return INVALID_OFFSET;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (candidateLast < m_StagingBufferFirst)
|
||||
return candidateOffset;
|
||||
else
|
||||
return INVALID_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
} // namespace Renderer
|
132
source/renderer/backend/vulkan/RingCommandContext.h
Normal file
132
source/renderer/backend/vulkan/RingCommandContext.h
Normal 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
|
140
source/renderer/backend/vulkan/SamplerManager.cpp
Normal file
140
source/renderer/backend/vulkan/SamplerManager.cpp
Normal 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
|
75
source/renderer/backend/vulkan/SamplerManager.h
Normal file
75
source/renderer/backend/vulkan/SamplerManager.h
Normal 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
|
699
source/renderer/backend/vulkan/ShaderProgram.cpp
Normal file
699
source/renderer/backend/vulkan/ShaderProgram.cpp
Normal 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
|
187
source/renderer/backend/vulkan/ShaderProgram.h
Normal file
187
source/renderer/backend/vulkan/ShaderProgram.h
Normal 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
|
177
source/renderer/backend/vulkan/SubmitScheduler.cpp
Normal file
177
source/renderer/backend/vulkan/SubmitScheduler.cpp
Normal 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
|
117
source/renderer/backend/vulkan/SubmitScheduler.h
Normal file
117
source/renderer/backend/vulkan/SubmitScheduler.h
Normal 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
|
365
source/renderer/backend/vulkan/SwapChain.cpp
Normal file
365
source/renderer/backend/vulkan/SwapChain.cpp
Normal 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
|
115
source/renderer/backend/vulkan/SwapChain.h
Normal file
115
source/renderer/backend/vulkan/SwapChain.h
Normal 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
|
298
source/renderer/backend/vulkan/Texture.cpp
Normal file
298
source/renderer/backend/vulkan/Texture.cpp
Normal 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
|
132
source/renderer/backend/vulkan/Texture.h
Normal file
132
source/renderer/backend/vulkan/Texture.h
Normal 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
|
170
source/renderer/backend/vulkan/Utilities.cpp
Normal file
170
source/renderer/backend/vulkan/Utilities.cpp
Normal 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
|
91
source/renderer/backend/vulkan/Utilities.h
Normal file
91
source/renderer/backend/vulkan/Utilities.h
Normal 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
|
22
source/renderer/backend/vulkan/VMA.cpp
Normal file
22
source/renderer/backend/vulkan/VMA.cpp
Normal 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"
|
80
source/renderer/backend/vulkan/VMA.h
Normal file
80
source/renderer/backend/vulkan/VMA.h
Normal 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
|
Loading…
Reference in New Issue
Block a user