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.
|
; glarb - GL with legacy assembler-like shaders, might used only for buggy drivers.
|
||||||
; gl - GL with GLSL shaders, should be used by default.
|
; gl - GL with GLSL shaders, should be used by default.
|
||||||
; dummy - backend that does nothing, allows to check performance without backend drivers.
|
; dummy - backend that does nothing, allows to check performance without backend drivers.
|
||||||
|
; vulkan - Vulkan with SPIR-V shaders.
|
||||||
rendererbackend = "gl"
|
rendererbackend = "gl"
|
||||||
|
|
||||||
; Enables additional debug information in renderer backend.
|
; Enables additional debug information in renderer backend.
|
||||||
@ -125,6 +126,9 @@ renderer.backend.debugmessages = "false"
|
|||||||
renderer.backend.debuglabels = "false"
|
renderer.backend.debuglabels = "false"
|
||||||
renderer.backend.debugscopedlabels = "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.
|
; Should not be edited. It's used only for preventing of running fixed pipeline.
|
||||||
renderpath = default
|
renderpath = default
|
||||||
|
|
||||||
@ -332,8 +336,7 @@ unload = "U" ; Unload garrisoned units when a building/mechanica
|
|||||||
unloadturrets = "U" ; Unload turreted units.
|
unloadturrets = "U" ; Unload turreted units.
|
||||||
leaveturret = "U" ; Leave turret point.
|
leaveturret = "U" ; Leave turret point.
|
||||||
move = "" ; Modifier to move to a point instead of another action (e.g. gather)
|
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 = Ctrl ; Modifier to attack instead of another action (e.g. capture)
|
||||||
attack = "" ; Modifier to attack instead of another action (e.g. capture)
|
|
||||||
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
|
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
|
||||||
attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units 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
|
garrison = Ctrl ; Modifier to garrison when clicking on building
|
||||||
|
@ -4,10 +4,32 @@
|
|||||||
#include "common/texture.h"
|
#include "common/texture.h"
|
||||||
#include "common/uniform.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) \
|
#define OUTPUT_FRAGMENT_SINGLE_COLOR(COLOR) \
|
||||||
gl_FragColor = COLOR
|
gl_FragColor = COLOR
|
||||||
|
|
||||||
#define OUTPUT_FRAGMENT_COLOR(LOCATION, COLOR) \
|
#define OUTPUT_FRAGMENT_COLOR(LOCATION, COLOR) \
|
||||||
gl_FragData[LOCATION] = COLOR
|
gl_FragData[LOCATION] = COLOR
|
||||||
|
|
||||||
|
#endif // USE_SPIRV
|
||||||
|
|
||||||
#endif // INCLUDED_COMMON_FRAGMENT
|
#endif // INCLUDED_COMMON_FRAGMENT
|
||||||
|
@ -3,7 +3,21 @@
|
|||||||
|
|
||||||
#include "common/uniform.h"
|
#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) \
|
#define VERTEX_OUTPUT(LOCATION, TYPE, NAME) \
|
||||||
varying TYPE NAME
|
varying TYPE NAME
|
||||||
|
|
||||||
|
#endif // USE_SPIRV
|
||||||
|
|
||||||
#endif // INCLUDED_COMMON_STAGE
|
#endif // INCLUDED_COMMON_STAGE
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
#ifndef INCLUDED_COMMON_TEXTURE
|
#ifndef INCLUDED_COMMON_TEXTURE
|
||||||
#define INCLUDED_COMMON_TEXTURE
|
#define INCLUDED_COMMON_TEXTURE
|
||||||
|
|
||||||
#define SAMPLE_2D texture2D
|
#if USE_SPIRV
|
||||||
#define SAMPLE_2D_SHADOW shadow2D
|
#define SAMPLE_2D texture
|
||||||
#define SAMPLE_CUBE textureCube
|
#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
|
#endif // INCLUDED_COMMON_TEXTURE
|
||||||
|
@ -1,6 +1,61 @@
|
|||||||
#ifndef INCLUDED_COMMON_UNIFORM
|
#ifndef INCLUDED_COMMON_UNIFORM
|
||||||
#define 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 BEGIN_DRAW_TEXTURES
|
||||||
#define END_DRAW_TEXTURES
|
#define END_DRAW_TEXTURES
|
||||||
#define NO_DRAW_TEXTURES
|
#define NO_DRAW_TEXTURES
|
||||||
@ -33,4 +88,6 @@
|
|||||||
#define UNIFORM(TYPE, NAME) \
|
#define UNIFORM(TYPE, NAME) \
|
||||||
uniform TYPE NAME;
|
uniform TYPE NAME;
|
||||||
|
|
||||||
|
#endif // USE_SPIRV
|
||||||
|
|
||||||
#endif // INCLUDED_COMMON_UNIFORM
|
#endif // INCLUDED_COMMON_UNIFORM
|
||||||
|
@ -3,10 +3,27 @@
|
|||||||
|
|
||||||
#include "common/uniform.h"
|
#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) \
|
#define VERTEX_INPUT_ATTRIBUTE(LOCATION, TYPE, NAME) \
|
||||||
attribute TYPE NAME
|
attribute TYPE NAME
|
||||||
|
|
||||||
#define OUTPUT_VERTEX_POSITION(position) \
|
#define OUTPUT_VERTEX_POSITION(position) \
|
||||||
gl_Position = position
|
gl_Position = position
|
||||||
|
|
||||||
|
#endif // USE_SPIRV
|
||||||
|
|
||||||
#endif // INCLUDED_COMMON_VERTEX
|
#endif // INCLUDED_COMMON_VERTEX
|
||||||
|
@ -191,7 +191,8 @@
|
|||||||
"config": "rendererbackend",
|
"config": "rendererbackend",
|
||||||
"list": [
|
"list": [
|
||||||
{ "value": "gl", "label": "OpenGL", "tooltip": "Default OpenGL backend with GLSL. REQUIRES GAME RESTART" },
|
{ "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();
|
viewportRect.height = m_Texture->GetHeight();
|
||||||
deviceCommandContext->SetViewports(1, &viewportRect);
|
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[] =
|
float quadVerts[] =
|
||||||
{
|
{
|
||||||
1.0f, 1.0f,
|
1.0f, 1.0f,
|
||||||
@ -187,13 +192,13 @@ void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* devic
|
|||||||
};
|
};
|
||||||
float quadTex[] =
|
float quadTex[] =
|
||||||
{
|
{
|
||||||
1.0f, 1.0f,
|
1.0f, topV,
|
||||||
0.0f, 1.0f,
|
0.0f, topV,
|
||||||
0.0f, 0.0f,
|
0.0f, bottomV,
|
||||||
|
|
||||||
0.0f, 0.0f,
|
0.0f, bottomV,
|
||||||
1.0f, 0.0f,
|
1.0f, bottomV,
|
||||||
1.0f, 1.0f
|
1.0f, topV
|
||||||
};
|
};
|
||||||
|
|
||||||
deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
|
deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
|
||||||
|
@ -251,8 +251,11 @@ CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
|
|||||||
}};
|
}};
|
||||||
m_QuadVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
|
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();
|
const uint32_t stride = m_VertexArray.GetStride();
|
||||||
if (g_VideoMode.GetBackendDevice()->GetCapabilities().instancing)
|
if (device->GetCapabilities().instancing)
|
||||||
{
|
{
|
||||||
m_UseInstancing = true;
|
m_UseInstancing = true;
|
||||||
|
|
||||||
|
@ -58,6 +58,8 @@ public:
|
|||||||
|
|
||||||
const CTexturePtr& GetTexture() const { return m_FinalTexture; }
|
const CTexturePtr& GetTexture() const { return m_FinalTexture; }
|
||||||
|
|
||||||
|
bool IsFlipped() const { return m_Flipped; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The maximum height for unit passage in water.
|
* @return The maximum height for unit passage in water.
|
||||||
*/
|
*/
|
||||||
@ -96,6 +98,7 @@ private:
|
|||||||
bool m_TerrainTextureDirty = true;
|
bool m_TerrainTextureDirty = true;
|
||||||
bool m_FinalTextureDirty = true;
|
bool m_FinalTextureDirty = true;
|
||||||
double m_LastFinalTextureUpdate = 0.0;
|
double m_LastFinalTextureUpdate = 0.0;
|
||||||
|
bool m_Flipped = false;
|
||||||
|
|
||||||
// minimap texture handles
|
// minimap texture handles
|
||||||
std::unique_ptr<Renderer::Backend::ITexture> m_TerrainTexture;
|
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.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 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)
|
if (device->GetBackend() != Renderer::Backend::Backend::GL)
|
||||||
isUsable = false;
|
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())
|
else if (!attrs.GetNamedItem(at_context).empty())
|
||||||
{
|
{
|
||||||
CStr cond = attrs.GetNamedItem(at_context);
|
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.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 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 CVector2D center = m_CachedActualSize.CenterPoint();
|
||||||
const CRect source(
|
const CRect source(
|
||||||
0, miniMapTexture.GetTexture()->GetHeight(),
|
0,
|
||||||
miniMapTexture.GetTexture()->GetWidth(), 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 CSize2D size(m_CachedActualSize.GetSize() / m_MapScale);
|
||||||
const CRect destination(center - size / 2.0f, size);
|
const CRect destination(center - size / 2.0f, size);
|
||||||
canvas.DrawRotatedTexture(
|
canvas.DrawRotatedTexture(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2022 Wildfire Games.
|
/* Copyright (C) 2023 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 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_CUSTOM)
|
||||||
X(RENDER_DEBUG_MODE_NONE)
|
X(RENDER_DEBUG_MODE_NONE)
|
||||||
X(SHADOWS_CASCADE_COUNT)
|
X(SHADOWS_CASCADE_COUNT)
|
||||||
|
X(USE_DESCRIPTOR_INDEXING)
|
||||||
X(USE_FANCY_EFFECTS)
|
X(USE_FANCY_EFFECTS)
|
||||||
X(USE_FP_SHADOW)
|
X(USE_FP_SHADOW)
|
||||||
X(USE_GPU_INSTANCING)
|
X(USE_GPU_INSTANCING)
|
||||||
|
@ -38,6 +38,50 @@
|
|||||||
|
|
||||||
#include <string_view>
|
#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()
|
CPostprocManager::CPostprocManager()
|
||||||
: m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true),
|
: m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true),
|
||||||
m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0)
|
m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0)
|
||||||
@ -140,7 +184,9 @@ void CPostprocManager::RecreateBuffers()
|
|||||||
name = backendDevice->CreateTexture2D( \
|
name = backendDevice->CreateTexture2D( \
|
||||||
"PostProc" #name, \
|
"PostProc" #name, \
|
||||||
Renderer::Backend::ITexture::Usage::SAMPLED | \
|
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::Format::R8G8B8A8_UNORM, w, h, \
|
||||||
Renderer::Backend::Sampler::MakeDefaultSampler( \
|
Renderer::Backend::Sampler::MakeDefaultSampler( \
|
||||||
Renderer::Backend::Sampler::Filter::LINEAR, \
|
Renderer::Backend::Sampler::Filter::LINEAR, \
|
||||||
@ -247,36 +293,7 @@ void CPostprocManager::ApplyBlurDownscale2x(
|
|||||||
deviceCommandContext->SetTexture(
|
deviceCommandContext->SetTexture(
|
||||||
shader->GetBindingSlot(str_renderedTex), inTex);
|
shader->GetBindingSlot(str_renderedTex), inTex);
|
||||||
|
|
||||||
// TODO: remove the fullscreen quad drawing duplication.
|
DrawFullscreenQuad(m_VertexInputLayout, 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
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
|
|
||||||
deviceCommandContext->EndPass();
|
deviceCommandContext->EndPass();
|
||||||
deviceCommandContext->EndFramebufferPass();
|
deviceCommandContext->EndFramebufferPass();
|
||||||
@ -311,35 +328,7 @@ void CPostprocManager::ApplyBlurGauss(
|
|||||||
deviceCommandContext->SetUniform(
|
deviceCommandContext->SetUniform(
|
||||||
shader->GetBindingSlot(str_texSize), inWidth, inHeight);
|
shader->GetBindingSlot(str_texSize), inWidth, inHeight);
|
||||||
|
|
||||||
float quadVerts[] =
|
DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
deviceCommandContext->EndPass();
|
deviceCommandContext->EndPass();
|
||||||
deviceCommandContext->EndFramebufferPass();
|
deviceCommandContext->EndFramebufferPass();
|
||||||
@ -364,14 +353,7 @@ void CPostprocManager::ApplyBlurGauss(
|
|||||||
deviceCommandContext->SetUniform(
|
deviceCommandContext->SetUniform(
|
||||||
shader->GetBindingSlot(str_texSize), inWidth, inHeight);
|
shader->GetBindingSlot(str_texSize), inWidth, inHeight);
|
||||||
|
|
||||||
deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
|
DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
|
||||||
|
|
||||||
deviceCommandContext->SetVertexBufferData(
|
|
||||||
0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
|
|
||||||
deviceCommandContext->SetVertexBufferData(
|
|
||||||
1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
|
|
||||||
|
|
||||||
deviceCommandContext->Draw(0, 6);
|
|
||||||
|
|
||||||
deviceCommandContext->EndPass();
|
deviceCommandContext->EndPass();
|
||||||
deviceCommandContext->EndFramebufferPass();
|
deviceCommandContext->EndFramebufferPass();
|
||||||
@ -466,35 +448,7 @@ void CPostprocManager::ApplyEffect(
|
|||||||
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation);
|
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation);
|
||||||
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom);
|
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom);
|
||||||
|
|
||||||
float quadVerts[] =
|
DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
deviceCommandContext->EndPass();
|
deviceCommandContext->EndPass();
|
||||||
deviceCommandContext->EndFramebufferPass();
|
deviceCommandContext->EndFramebufferPass();
|
||||||
@ -663,7 +617,8 @@ void CPostprocManager::CreateMultisampleBuffer()
|
|||||||
|
|
||||||
m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS",
|
m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS",
|
||||||
Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
|
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::Format::R8G8B8A8_UNORM, m_Width, m_Height,
|
||||||
Renderer::Backend::Sampler::MakeDefaultSampler(
|
Renderer::Backend::Sampler::MakeDefaultSampler(
|
||||||
Renderer::Backend::Sampler::Filter::LINEAR,
|
Renderer::Backend::Sampler::Filter::LINEAR,
|
||||||
@ -672,7 +627,8 @@ void CPostprocManager::CreateMultisampleBuffer()
|
|||||||
// Allocate the Depth/Stencil texture.
|
// Allocate the Depth/Stencil texture.
|
||||||
m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS",
|
m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS",
|
||||||
Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
|
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::Format::D24_S8, m_Width, m_Height,
|
||||||
Renderer::Backend::Sampler::MakeDefaultSampler(
|
Renderer::Backend::Sampler::MakeDefaultSampler(
|
||||||
Renderer::Backend::Sampler::Filter::LINEAR,
|
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.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 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]() {
|
m_ConfigHooks->Setup("gpuskinning", [this]() {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
CFG_GET_VAL("gpuskinning", enabled);
|
CFG_GET_VAL("gpuskinning", enabled);
|
||||||
if (enabled && g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB)
|
if (enabled)
|
||||||
LOGWARNING("GPUSkinning has been disabled, because it is not supported with ARB shaders.");
|
{
|
||||||
else if (enabled)
|
if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB)
|
||||||
m_GPUSkinning = true;
|
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);
|
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
|
// Save the model-view-projection matrix so the shaders can use it for projective texturing
|
||||||
wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection();
|
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 vpHeight = wm.m_RefTextureSize;
|
||||||
float vpWidth = wm.m_RefTextureSize;
|
float vpWidth = wm.m_RefTextureSize;
|
||||||
@ -643,6 +650,15 @@ void CSceneRenderer::RenderRefractions(
|
|||||||
wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse();
|
wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse();
|
||||||
wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation();
|
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 vpHeight = wm.m_RefTextureSize;
|
||||||
float vpWidth = 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
|
// inverted depth test so any behind an occluder will get drawn in a constant
|
||||||
// color.
|
// color.
|
||||||
|
|
||||||
|
// TODO: do we need clear here?
|
||||||
deviceCommandContext->ClearFramebuffer(false, true, true);
|
deviceCommandContext->ClearFramebuffer(false, true, true);
|
||||||
|
|
||||||
// Render occluders:
|
// Render occluders:
|
||||||
|
@ -460,6 +460,15 @@ void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
|
|||||||
lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
|
lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
|
||||||
lightToTex._44 = 1.0;
|
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;
|
Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,10 +57,7 @@ struct SWavesVertex
|
|||||||
CVector3D m_RetreatPosition;
|
CVector3D m_RetreatPosition;
|
||||||
|
|
||||||
CVector2D m_PerpVect;
|
CVector2D m_PerpVect;
|
||||||
u8 m_UV[3];
|
float m_UV[2];
|
||||||
|
|
||||||
// pad to a power of two
|
|
||||||
u8 m_Padding[5];
|
|
||||||
};
|
};
|
||||||
cassert(sizeof(SWavesVertex) == 64);
|
cassert(sizeof(SWavesVertex) == 64);
|
||||||
|
|
||||||
@ -143,7 +140,7 @@ void WaterManager::Initialize()
|
|||||||
offsetof(SWavesVertex, m_PerpVect), stride,
|
offsetof(SWavesVertex, m_PerpVect), stride,
|
||||||
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
|
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
|
||||||
{Renderer::Backend::VertexAttributeStream::UV0,
|
{Renderer::Backend::VertexAttributeStream::UV0,
|
||||||
Renderer::Backend::Format::R8G8_UINT,
|
Renderer::Backend::Format::R32G32_SFLOAT,
|
||||||
offsetof(SWavesVertex, m_UV), stride,
|
offsetof(SWavesVertex, m_UV), stride,
|
||||||
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
|
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.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
@ -62,6 +62,15 @@ enum class Format
|
|||||||
BC3_UNORM
|
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 Backend
|
||||||
|
|
||||||
} // namespace Renderer
|
} // namespace Renderer
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2022 Wildfire Games.
|
/* Copyright (C) 2023 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
@ -34,7 +34,9 @@ public:
|
|||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
VERTEX,
|
VERTEX,
|
||||||
INDEX
|
INDEX,
|
||||||
|
UPLOAD,
|
||||||
|
UNIFORM,
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual Type GetType() const = 0;
|
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.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 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,
|
CDevice* device, const char* name,
|
||||||
const Type type, const uint32_t size, const bool dynamic)
|
const Type type, const uint32_t size, const bool dynamic)
|
||||||
{
|
{
|
||||||
|
ENSURE(type == Type::VERTEX || type == Type::INDEX);
|
||||||
std::unique_ptr<CBuffer> buffer(new CBuffer());
|
std::unique_ptr<CBuffer> buffer(new CBuffer());
|
||||||
buffer->m_Device = device;
|
buffer->m_Device = device;
|
||||||
buffer->m_Type = type;
|
buffer->m_Type = type;
|
||||||
|
@ -103,6 +103,10 @@ GLenum BufferTypeToGLTarget(const CBuffer::Type type)
|
|||||||
case CBuffer::Type::INDEX:
|
case CBuffer::Type::INDEX:
|
||||||
target = GL_ELEMENT_ARRAY_BUFFER;
|
target = GL_ELEMENT_ARRAY_BUFFER;
|
||||||
break;
|
break;
|
||||||
|
case CBuffer::Type::UPLOAD:
|
||||||
|
case CBuffer::Type::UNIFORM:
|
||||||
|
debug_warn("Unsupported buffer type.");
|
||||||
|
break;
|
||||||
};
|
};
|
||||||
return target;
|
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 "Device.h"
|
||||||
|
|
||||||
#include "lib/external_libraries/libsdl.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/JSON.h"
|
||||||
#include "scriptinterface/Object.h"
|
#include "scriptinterface/Object.h"
|
||||||
#include "scriptinterface/ScriptInterface.h"
|
#include "scriptinterface/ScriptInterface.h"
|
||||||
#include "scriptinterface/ScriptRequest.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>
|
#include <SDL_vulkan.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -38,59 +68,620 @@ namespace Backend
|
|||||||
namespace Vulkan
|
namespace Vulkan
|
||||||
{
|
{
|
||||||
|
|
||||||
// static
|
namespace
|
||||||
std::unique_ptr<CDevice> CDevice::Create(SDL_Window* UNUSED(window))
|
|
||||||
{
|
{
|
||||||
|
|
||||||
|
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());
|
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;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
CDevice::CDevice() = default;
|
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)
|
void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
|
||||||
{
|
{
|
||||||
Script::SetProperty(rq, settings, "name", "vulkan");
|
Script::SetProperty(rq, settings, "name", "vulkan");
|
||||||
|
|
||||||
std::string vulkanSupport = "unsupported";
|
Script::SetProperty(rq, settings, "extensions", m_Extensions);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<IDeviceCommandContext> CDevice::CreateCommandContext()
|
JS::RootedValue device(rq.cx);
|
||||||
{
|
Script::CreateObject(rq, &device);
|
||||||
return nullptr;
|
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(
|
std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
|
||||||
const SGraphicsPipelineStateDesc& pipelineStateDesc)
|
const SGraphicsPipelineStateDesc& pipelineStateDesc)
|
||||||
{
|
{
|
||||||
UNUSED2(pipelineStateDesc);
|
UNUSED2(pipelineStateDesc);
|
||||||
return nullptr;
|
return CGraphicsPipelineState::Create(this, pipelineStateDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IVertexInputLayout> CDevice::CreateVertexInputLayout(
|
std::unique_ptr<IVertexInputLayout> CDevice::CreateVertexInputLayout(
|
||||||
const PS::span<const SVertexAttributeFormat> attributes)
|
const PS::span<const SVertexAttributeFormat> attributes)
|
||||||
{
|
{
|
||||||
UNUSED2(attributes);
|
return std::make_unique<CVertexInputLayout>(this, attributes);
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ITexture> CDevice::CreateTexture(
|
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 Format format, const uint32_t width, const uint32_t height,
|
||||||
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount)
|
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount)
|
||||||
{
|
{
|
||||||
UNUSED2(name);
|
return CTexture::Create(
|
||||||
UNUSED2(type);
|
this, name, type, usage, format, width, height,
|
||||||
UNUSED2(usage);
|
defaultSamplerDesc, MIPLevelCount, sampleCount);
|
||||||
UNUSED2(format);
|
|
||||||
UNUSED2(width);
|
|
||||||
UNUSED2(height);
|
|
||||||
UNUSED2(defaultSamplerDesc);
|
|
||||||
UNUSED2(MIPLevelCount);
|
|
||||||
UNUSED2(sampleCount);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ITexture> CDevice::CreateTexture2D(
|
std::unique_ptr<ITexture> CDevice::CreateTexture2D(
|
||||||
@ -124,33 +708,46 @@ std::unique_ptr<IFramebuffer> CDevice::CreateFramebuffer(
|
|||||||
const char* name, SColorAttachment* colorAttachment,
|
const char* name, SColorAttachment* colorAttachment,
|
||||||
SDepthStencilAttachment* depthStencilAttachment)
|
SDepthStencilAttachment* depthStencilAttachment)
|
||||||
{
|
{
|
||||||
UNUSED2(name);
|
return CFramebuffer::Create(
|
||||||
UNUSED2(colorAttachment);
|
this, name, colorAttachment, depthStencilAttachment);
|
||||||
UNUSED2(depthStencilAttachment);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IBuffer> CDevice::CreateBuffer(
|
std::unique_ptr<IBuffer> CDevice::CreateBuffer(
|
||||||
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic)
|
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic)
|
||||||
{
|
{
|
||||||
UNUSED2(name);
|
return CreateCBuffer(name, type, size, dynamic);
|
||||||
UNUSED2(type);
|
}
|
||||||
UNUSED2(size);
|
|
||||||
UNUSED2(dynamic);
|
std::unique_ptr<CBuffer> CDevice::CreateCBuffer(
|
||||||
return nullptr;
|
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(
|
std::unique_ptr<IShaderProgram> CDevice::CreateShaderProgram(
|
||||||
const CStr& name, const CShaderDefines& defines)
|
const CStr& name, const CShaderDefines& defines)
|
||||||
{
|
{
|
||||||
UNUSED2(name);
|
return CShaderProgram::Create(this, name, defines);
|
||||||
UNUSED2(defines);
|
}
|
||||||
return nullptr;
|
|
||||||
|
std::unique_ptr<IDeviceCommandContext> CDevice::CreateCommandContext()
|
||||||
|
{
|
||||||
|
return CDeviceCommandContext::Create(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDevice::AcquireNextBackbuffer()
|
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(
|
IFramebuffer* CDevice::GetCurrentBackbuffer(
|
||||||
@ -159,15 +756,24 @@ IFramebuffer* CDevice::GetCurrentBackbuffer(
|
|||||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||||
const AttachmentStoreOp depthStencilAttachmentStoreOp)
|
const AttachmentStoreOp depthStencilAttachmentStoreOp)
|
||||||
{
|
{
|
||||||
UNUSED2(colorAttachmentLoadOp);
|
return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbuffer(
|
||||||
UNUSED2(colorAttachmentStoreOp);
|
colorAttachmentLoadOp, colorAttachmentStoreOp,
|
||||||
UNUSED2(depthStencilAttachmentLoadOp);
|
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp) : nullptr;
|
||||||
UNUSED2(depthStencilAttachmentStoreOp);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDevice::Present()
|
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)
|
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
|
bool CDevice::IsTextureFormatSupported(const Format format) const
|
||||||
{
|
{
|
||||||
UNUSED2(format);
|
bool supported = false;
|
||||||
return 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
|
bool CDevice::IsFramebufferFormatSupported(const Format format) const
|
||||||
{
|
{
|
||||||
UNUSED2(format);
|
VkFormatProperties formatProperties{};
|
||||||
return false;
|
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(
|
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);
|
// TODO: account usage.
|
||||||
UNUSED2(depth);
|
ENSURE(depth || stencil);
|
||||||
UNUSED2(stencil);
|
Format format = Format::UNDEFINED;
|
||||||
return 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)
|
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window)
|
||||||
|
@ -20,9 +20,19 @@
|
|||||||
|
|
||||||
#include "renderer/backend/IDevice.h"
|
#include "renderer/backend/IDevice.h"
|
||||||
#include "renderer/backend/vulkan/DeviceForward.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 "scriptinterface/ScriptForward.h"
|
||||||
|
|
||||||
|
#include <glad/vulkan.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <limits>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
typedef struct SDL_Window SDL_Window;
|
typedef struct SDL_Window SDL_Window;
|
||||||
|
|
||||||
@ -35,7 +45,18 @@ namespace Backend
|
|||||||
namespace Vulkan
|
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:
|
public:
|
||||||
/**
|
/**
|
||||||
@ -79,6 +100,9 @@ public:
|
|||||||
std::unique_ptr<IBuffer> CreateBuffer(
|
std::unique_ptr<IBuffer> CreateBuffer(
|
||||||
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override;
|
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(
|
std::unique_ptr<IShaderProgram> CreateShaderProgram(
|
||||||
const CStr& name, const CShaderDefines& defines) override;
|
const CStr& name, const CShaderDefines& defines) override;
|
||||||
|
|
||||||
@ -103,15 +127,88 @@ public:
|
|||||||
|
|
||||||
const Capabilities& GetCapabilities() const override { return m_Capabilities; }
|
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:
|
private:
|
||||||
CDevice();
|
CDevice();
|
||||||
|
|
||||||
|
void RecreateSwapChain();
|
||||||
|
bool IsSwapChainValid();
|
||||||
|
void ProcessObjectToDestroyQueue(const bool ignoreFrameID = false);
|
||||||
|
void ProcessTextureToDestroyQueue(const bool ignoreFrameID = false);
|
||||||
|
|
||||||
std::string m_Name;
|
std::string m_Name;
|
||||||
std::string m_Version;
|
std::string m_Version;
|
||||||
|
std::string m_VendorID;
|
||||||
std::string m_DriverInformation;
|
std::string m_DriverInformation;
|
||||||
std::vector<std::string> m_Extensions;
|
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{};
|
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
|
} // 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