Adds compute shaders support and scaling with FSR.

Fixes #6842

Comments By: phosit, Stan
Differential Revision: https://code.wildfiregames.com/D5218
This was SVN commit r28010.
This commit is contained in:
Vladislav Belov 2024-01-17 19:40:27 +00:00
parent f9f798158a
commit e3f46bb809
63 changed files with 5337 additions and 118 deletions

View File

@ -104,6 +104,9 @@ shadowscutoffdistance = 300.0
; If true shadows cover the whole map instead of the camera frustum.
shadowscovermap = false
renderer.scale = 1.0
renderer.upscale.technique = "fsr"
vsync = false
particles = true
fog = true

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<effect>
<technique>
<require shaders="glsl"/>
<compute shader="glsl/compute_downscale"/>
</technique>
<technique>
<require shaders="spirv"/>
<compute shader="spirv/compute_downscale"/>
</technique>
</effect>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<effect>
<technique>
<require shaders="glsl"/>
<compute shader="glsl/compute_rcas"/>
</technique>
<technique>
<require shaders="spirv"/>
<compute shader="spirv/compute_rcas"/>
</technique>
</effect>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<effect>
<technique>
<require shaders="glsl"/>
<compute shader="glsl/compute_upscale_fsr"/>
</technique>
<technique>
<require shaders="spirv"/>
<compute shader="spirv/compute_upscale_fsr"/>
</technique>
</effect>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<effect>
<technique>
<require shaders="glsl"/>
<pass shader="glsl/upscale_bilinear">
<depth test="FALSE" mask="false"/>
</pass>
</technique>
<technique>
<require shaders="spirv"/>
<pass shader="spirv/upscale_bilinear">
<depth test="FALSE" mask="false"/>
</pass>
</technique>
</effect>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<effect>
<technique>
<require shaders="glsl"/>
<pass shader="glsl/upscale_nearest">
<depth test="FALSE" mask="false"/>
</pass>
</technique>
<technique>
<require shaders="spirv"/>
<pass shader="spirv/upscale_nearest">
<depth test="FALSE" mask="false"/>
</pass>
</technique>
</effect>

View File

@ -0,0 +1,22 @@
#ifndef INCLUDED_COMMON_COMPUTE
#define INCLUDED_COMMON_COMPUTE
#include "common/descriptor_indexing.h"
#include "common/texture.h"
#include "common/uniform.h"
#if STAGE_COMPUTE
#if USE_SPIRV
#define STORAGE_2D(LOCATION, FORMAT, NAME) \
layout(set = 2, binding = LOCATION, FORMAT) uniform image2D NAME
#else
// We use offset to the binding slot for OpenGL to avoid overlapping with other
// textures as OpenGL doesn't have sets.
#define STORAGE_2D(LOCATION, FORMAT, NAME) \
layout(binding = LOCATION, FORMAT) uniform image2D NAME
#endif
#endif // STAGE_COMPUTE
#endif // INCLUDED_COMMON_COMPUTE

View File

@ -0,0 +1,12 @@
#ifndef INCLUDED_COMMON_DESCRIPTOR_INDEXING
#define INCLUDED_COMMON_DESCRIPTOR_INDEXING
#if USE_SPIRV && 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_SPIRV && USE_DESCRIPTOR_INDEXING
#endif // INCLUDED_COMMON_DESCRIPTOR_INDEXING

View File

@ -1,19 +1,12 @@
#ifndef INCLUDED_COMMON_FRAGMENT
#define INCLUDED_COMMON_FRAGMENT
#include "common/descriptor_indexing.h"
#include "common/texture.h"
#include "common/uniform.h"
#if USE_SPIRV
#if USE_DESCRIPTOR_INDEXING
#extension GL_EXT_nonuniform_qualifier : enable
const int DESCRIPTOR_INDEXING_SET_SIZE = 16384;
layout (set = 0, binding = 0) uniform sampler2D textures2D[DESCRIPTOR_INDEXING_SET_SIZE];
layout (set = 0, binding = 1) uniform samplerCube texturesCube[DESCRIPTOR_INDEXING_SET_SIZE];
layout (set = 0, binding = 2) uniform sampler2DShadow texturesShadow[DESCRIPTOR_INDEXING_SET_SIZE];
#endif // USE_DESCRIPTOR_INDEXING
layout (location = 0) out vec4 fragmentColor;
#define OUTPUT_FRAGMENT_SINGLE_COLOR(COLOR) \

View File

@ -22,7 +22,7 @@
#define END_DRAW_TEXTURES
#define NO_DRAW_TEXTURES
#if STAGE_FRAGMENT
#if STAGE_FRAGMENT || STAGE_COMPUTE
#define TEXTURE_2D(LOCATION, NAME) \
layout (set = 1, binding = LOCATION) uniform sampler2D NAME;
#define TEXTURE_2D_SHADOW(LOCATION, NAME) \
@ -60,7 +60,7 @@
#define END_DRAW_TEXTURES
#define NO_DRAW_TEXTURES
#if STAGE_FRAGMENT
#if STAGE_FRAGMENT || STAGE_COMPUTE
#define TEXTURE_2D(LOCATION, NAME) \
uniform sampler2D NAME;
#define TEXTURE_2D_SHADOW(LOCATION, NAME) \

View File

@ -0,0 +1,23 @@
#version 430
#include "common/compute.h"
BEGIN_DRAW_TEXTURES
TEXTURE_2D(0, inTex)
END_DRAW_TEXTURES
BEGIN_DRAW_UNIFORMS
UNIFORM(vec4, screenSize)
END_DRAW_UNIFORMS
STORAGE_2D(0, rgba8, outTex);
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
void main()
{
ivec2 position = ivec2(gl_GlobalInvocationID.xy);
if (any(greaterThanEqual(position, ivec2(screenSize.zw))))
return;
vec2 uv = (vec2(position) + vec2(0.5, 0.5)) / screenSize.zw;
imageStore(outTex, position, texture(GET_DRAW_TEXTURE_2D(inTex), uv));
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="glsl">
<compute file="glsl/compute_downscale.cs"/>
</program>

View File

@ -0,0 +1,50 @@
#version 430
#include "common/compute.h"
BEGIN_DRAW_TEXTURES
TEXTURE_2D(0, inTex)
END_DRAW_TEXTURES
BEGIN_DRAW_UNIFORMS
UNIFORM(float, sharpness)
END_DRAW_UNIFORMS
STORAGE_2D(0, rgba8, outTex);
#define A_GPU 1
#define A_GLSL 1
#define FSR_RCAS_DENOISE 1
// TODO: support 16-bit floats.
#include "ffx_a.h"
#define FSR_RCAS_F 1
AF4 FsrRcasLoadF(ASU2 p) { return texelFetch(GET_DRAW_TEXTURE_2D(inTex), ASU2(p), 0); }
void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {}
#include "ffx_fsr1.h"
void CurrFilter(AU2 pos)
{
AU4 const0;
FsrRcasCon(const0, sharpness);
AF3 c;
FsrRcasF(c.r, c.g, c.b, pos, const0);
imageStore(outTex, ASU2(pos), AF4(c, 1));
}
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
void main()
{
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
CurrFilter(gxy);
gxy.x += 8u;
CurrFilter(gxy);
gxy.y += 8u;
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="glsl">
<compute file="glsl/compute_rcas.cs"/>
</program>

View File

@ -0,0 +1,54 @@
#version 430
#include "common/compute.h"
BEGIN_DRAW_TEXTURES
TEXTURE_2D(0, inTex)
END_DRAW_TEXTURES
BEGIN_DRAW_UNIFORMS
UNIFORM(vec4, screenSize)
END_DRAW_UNIFORMS
STORAGE_2D(0, rgba8, outTex);
#define A_GPU 1
#define A_GLSL 1
// TODO: support 16-bit floats.
#include "ffx_a.h"
#define FSR_EASU_F 1
AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(GET_DRAW_TEXTURE_2D(inTex), p, 0); return res; }
AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(GET_DRAW_TEXTURE_2D(inTex), p, 1); return res; }
AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(GET_DRAW_TEXTURE_2D(inTex), p, 2); return res; }
#include "ffx_fsr1.h"
void CurrFilter(AU2 pos)
{
uvec4 const0, const1, const2, const3;
FsrEasuCon(
const0, const1, const2, const3,
screenSize.x, screenSize.y,
screenSize.x, screenSize.y,
screenSize.z, screenSize.w);
AF3 c;
FsrEasuF(c, pos, const0, const1, const2, const3);
imageStore(outTex, ASU2(pos), AF4(c, 1));
}
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
void main()
{
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
CurrFilter(gxy);
gxy.x += 8u;
CurrFilter(gxy);
gxy.y += 8u;
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="glsl">
<compute file="glsl/compute_upscale_fsr.cs"/>
</program>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
#version 120
#include "common/fragment.h"
#include "common/stage.h"
BEGIN_DRAW_TEXTURES
TEXTURE_2D(0, inTex)
END_DRAW_TEXTURES
BEGIN_DRAW_UNIFORMS
UNIFORM(vec4, screenSize)
END_DRAW_UNIFORMS
VERTEX_OUTPUT(0, vec2, v_tex);
void main()
{
OUTPUT_FRAGMENT_SINGLE_COLOR(SAMPLE_2D(GET_DRAW_TEXTURE_2D(inTex), v_tex));
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="glsl">
<vertex file="glsl/simple.vs">
<stream name="pos" attribute="a_vertex"/>
<stream name="uv0" attribute="a_uv0"/>
</vertex>
<fragment file="glsl/upscale_bilinear.fs"/>
</program>

View File

@ -0,0 +1,19 @@
#version 130
#include "common/fragment.h"
#include "common/stage.h"
BEGIN_DRAW_TEXTURES
TEXTURE_2D(0, inTex)
END_DRAW_TEXTURES
BEGIN_DRAW_UNIFORMS
UNIFORM(vec4, screenSize)
END_DRAW_UNIFORMS
VERTEX_OUTPUT(0, vec2, v_tex);
void main()
{
OUTPUT_FRAGMENT_SINGLE_COLOR(texelFetch(GET_DRAW_TEXTURE_2D(inTex), ivec2(v_tex * screenSize.xy), 0));
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="glsl">
<vertex file="glsl/simple.vs">
<stream name="pos" attribute="a_vertex"/>
<stream name="uv0" attribute="a_uv0"/>
</vertex>
<fragment file="glsl/upscale_nearest.fs"/>
</program>

View File

@ -43,17 +43,26 @@
<value>glsl</value>
</attribute>
<element name="vertex">
<attribute name="file"><text/></attribute>
<zeroOrMore>
<ref name="streamContent"/>
</zeroOrMore>
</element>
<zeroOrMore>
<element name="vertex">
<attribute name="file"><text/></attribute>
<zeroOrMore>
<ref name="streamContent"/>
</zeroOrMore>
</element>
</zeroOrMore>
<element name="fragment">
<attribute name="file"><text/></attribute>
</element>
<zeroOrMore>
<element name="fragment">
<attribute name="file"><text/></attribute>
</element>
</zeroOrMore>
<zeroOrMore>
<element name="compute">
<attribute name="file"><text/></attribute>
</element>
</zeroOrMore>
</group>
</choice>
</element>

View File

@ -333,7 +333,11 @@ function enableButtons()
{
const availableOps = {
"==": (config, value) => config == value,
"!=": (config, value) => config != value
"!=": (config, value) => config != value,
"<": (config, value) => +config < +value,
"<=": (config, value) => +config <= +value,
">": (config, value) => +config > +value,
">=": (config, value) => +config >= +value
};
const op = availableOps[dependency.op] || availableOps["=="];
return op(Engine.ConfigDB_GetValue("user", dependency.config), dependency.value);

View File

@ -111,6 +111,35 @@
"tooltip": "Use screen-space post-processing filters (HDR, Bloom, DOF, etc).",
"config": "postproc"
},
{
"type": "dropdownNumber",
"label": "Resolution scale",
"tooltip": "A smaller scale makes rendering faster but produces a more blurry picture, a large scale makes rendering slower but produces a better picture.",
"dependencies": ["postproc"],
"config": "renderer.scale",
"list": [
{ "value": 0.5, "label": "50%" },
{ "value": 0.75, "label": "75%" },
{ "value": 0.875, "label": "87.5%" },
{ "value": 1.00, "label": "100%" },
{ "value": 1.25, "label": "125%" },
{ "value": 1.50, "label": "150%" },
{ "value": 1.75, "label": "175%" },
{ "value": 2.00, "label": "200%" }
]
},
{
"type": "dropdown",
"label": "Upscale technique",
"tooltip": "Technique defines performance and quality of upscaling process.",
"dependencies": ["postproc", { "config": "renderer.scale", "op": "<", "value": 1.0 }],
"config": "renderer.upscale.technique",
"list": [
{ "value": "fsr", "label": "FidelityFX Super Resolution 1.0", "tooltip": "Advanced upscale technique. For better results, use FSR with antialiasing enabled. Using it with the OpenGL backend may have some issues, consider using Vulkan backend instead." },
{ "value": "bilinear", "label": "Bilinear", "tooltip": "Bilinear upscale technique. Produces a slightly blurry picture depending on the scale." },
{ "value": "pixelated", "label": "Pixelated", "tooltip": "Simplest upscale technique. Used mostly for stylized effect." }
]
},
{
"type": "boolean",
"label": "Shadows",

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -173,6 +173,7 @@ bool CShaderManager::LoadTechnique(CShaderTechniquePtr& tech)
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
EL(blend);
EL(color);
EL(compute);
EL(cull);
EL(define);
EL(depth);
@ -269,6 +270,17 @@ bool CShaderManager::LoadTechnique(CShaderTechniquePtr& tech)
tech->SetSortByDistance(false);
const auto loadShaderProgramForTech = [&](const CStr& name, const CShaderDefines& defines)
{
CShaderProgramPtr shaderProgram = LoadProgram(name.c_str(), defines);
if (shaderProgram)
{
for (const VfsPath& shaderProgramPath : shaderProgram->GetFileDependencies())
AddTechniqueFileDependency(tech, shaderProgramPath);
}
return shaderProgram;
};
CShaderDefines techDefines = tech->GetShaderDefines();
XERO_ITER_EL((*usableTech), Child)
{
@ -430,11 +442,9 @@ bool CShaderManager::LoadTechnique(CShaderTechniquePtr& tech)
// Load the shader program after we've read all the possibly-relevant <define>s.
CShaderProgramPtr shaderProgram =
LoadProgram(Child.GetAttributes().GetNamedItem(at_shader).c_str(), passDefines);
loadShaderProgramForTech(Child.GetAttributes().GetNamedItem(at_shader), passDefines);
if (shaderProgram)
{
for (const VfsPath& shaderProgramPath : shaderProgram->GetFileDependencies())
AddTechniqueFileDependency(tech, shaderProgramPath);
if (tech->GetPipelineStateDescCallback())
tech->GetPipelineStateDescCallback()(passPipelineStateDesc);
passPipelineStateDesc.shaderProgram = shaderProgram->GetBackendShaderProgram();
@ -442,9 +452,22 @@ bool CShaderManager::LoadTechnique(CShaderTechniquePtr& tech)
m_Device->CreateGraphicsPipelineState(passPipelineStateDesc), shaderProgram);
}
}
else if (Child.GetNodeName() == el_compute)
{
CShaderProgramPtr shaderProgram =
loadShaderProgramForTech(Child.GetAttributes().GetNamedItem(at_shader), techDefines);
if (shaderProgram)
{
Renderer::Backend::SComputePipelineStateDesc computePipelineStateDesc{};
computePipelineStateDesc.shaderProgram = shaderProgram->GetBackendShaderProgram();
tech->SetComputePipelineState(
m_Device->CreateComputePipelineState(computePipelineStateDesc), shaderProgram);
}
}
}
tech->SetPasses(std::move(techPasses));
if (!techPasses.empty())
tech->SetPasses(std::move(techPasses));
return true;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,6 +21,7 @@
#include "graphics/ShaderDefines.h"
#include "graphics/ShaderProgram.h"
#include "graphics/ShaderTechnique.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/PipelineState.h"
#include <functional>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,6 +22,8 @@
#include "graphics/ShaderProgram.h"
#include "renderer/backend/IDevice.h"
#include <utility>
CShaderPass::CShaderPass(
std::unique_ptr<Renderer::Backend::IGraphicsPipelineState> pipelineState,
const CShaderProgramPtr& shader)
@ -39,9 +41,19 @@ CShaderTechnique::CShaderTechnique(
void CShaderTechnique::SetPasses(std::vector<CShaderPass>&& passes)
{
ENSURE(!m_ComputePipelineState);
m_Passes = std::move(passes);
}
void CShaderTechnique::SetComputePipelineState(
std::unique_ptr<Renderer::Backend::IComputePipelineState> pipelineState,
const CShaderProgramPtr& computeShader)
{
ENSURE(m_Passes.empty());
m_ComputePipelineState = std::move(pipelineState);
m_ComputeShader = computeShader;
}
int CShaderTechnique::GetNumPasses() const
{
return m_Passes.size();
@ -49,8 +61,16 @@ int CShaderTechnique::GetNumPasses() const
Renderer::Backend::IShaderProgram* CShaderTechnique::GetShader(int pass) const
{
ENSURE(0 <= pass && pass < (int)m_Passes.size());
return m_Passes[pass].GetPipelineState()->GetShaderProgram();
if (m_ComputeShader)
{
ENSURE(pass == 0);
return m_ComputeShader->GetBackendShaderProgram();
}
else
{
ENSURE(0 <= pass && pass < (int)m_Passes.size());
return m_Passes[pass].GetPipelineState()->GetShaderProgram();
}
}
Renderer::Backend::IGraphicsPipelineState*
@ -60,6 +80,12 @@ CShaderTechnique::GetGraphicsPipelineState(int pass) const
return m_Passes[pass].GetPipelineState();
}
Renderer::Backend::IComputePipelineState*
CShaderTechnique::GetComputePipelineState() const
{
return m_ComputePipelineState.get();
}
bool CShaderTechnique::GetSortByDistance() const
{
return m_SortByDistance;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -65,6 +65,9 @@ public:
CShaderTechnique(const VfsPath& path, const CShaderDefines& defines, const PipelineStateDescCallback& callback);
void SetPasses(std::vector<CShaderPass>&& passes);
void SetComputePipelineState(
std::unique_ptr<Renderer::Backend::IComputePipelineState> pipelineState,
const CShaderProgramPtr& computeShader);
int GetNumPasses() const;
@ -73,6 +76,9 @@ public:
Renderer::Backend::IGraphicsPipelineState*
GetGraphicsPipelineState(int pass = 0) const;
Renderer::Backend::IComputePipelineState*
GetComputePipelineState() const;
/**
* Whether this technique uses alpha blending that requires objects to be
* drawn from furthest to nearest.
@ -97,6 +103,9 @@ private:
CShaderDefines m_Defines;
PipelineStateDescCallback m_PipelineStateDescCallback;
std::unique_ptr<Renderer::Backend::IComputePipelineState> m_ComputePipelineState;
CShaderProgramPtr m_ComputeShader;
};
#endif // INCLUDED_SHADERTECHNIQUE

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -103,6 +103,8 @@ X(canvas2d)
X(color)
X(colorAdd)
X(colorMul)
X(compute_rcas)
X(compute_upscale_fsr)
X(debug_line)
X(debug_overlay)
X(delta)
@ -117,6 +119,7 @@ X(grayscaleFactor)
X(hdr)
X(height)
X(instancingTransform)
X(inTex)
X(losTex)
X(losTex1)
X(losTex2)
@ -134,6 +137,7 @@ X(normalMap2)
X(objectColor)
X(overlay_line)
X(overlay_solid)
X(outTex)
X(particle_add)
X(particle_multiply)
X(particle_overlay)
@ -177,6 +181,8 @@ X(time)
X(tint)
X(transform)
X(translation)
X(upscale_bilinear)
X(upscale_nearest)
X(viewInvTransform)
X(water_high)
X(water_simple)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -152,8 +152,7 @@ void CPostprocManager::Initialize()
[maxSamples](const uint32_t sampleCount) { return sampleCount <= maxSamples; } );
// The screen size starts out correct and then must be updated with Resize()
m_Width = g_Renderer.GetWidth();
m_Height = g_Renderer.GetHeight();
RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
RecreateBuffers();
m_IsInitialized = true;
@ -162,15 +161,20 @@ void CPostprocManager::Initialize()
UpdateAntiAliasingTechnique();
UpdateSharpeningTechnique();
UpdateSharpnessFactor();
CStr upscaleName;
CFG_GET_VAL("renderer.upscale.technique", upscaleName);
SetUpscaleTechnique(upscaleName);
// This might happen after the map is loaded and the effect chosen
SetPostEffect(m_PostProcEffect);
if (m_Device->GetCapabilities().computeShaders)
m_DownscaleComputeTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("compute_downscale"));
}
void CPostprocManager::Resize()
{
m_Width = g_Renderer.GetWidth();
m_Height = g_Renderer.GetHeight();
RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
// If the buffers were intialized, recreate them to the new size.
if (m_IsInitialized)
@ -197,6 +201,42 @@ void CPostprocManager::RecreateBuffers()
GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height);
GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height);
if (m_UnscaledWidth != m_Width && m_Device->GetCapabilities().computeShaders)
{
const uint32_t usage =
Renderer::Backend::ITexture::Usage::TRANSFER_SRC |
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT |
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::STORAGE;
m_UnscaledTexture1 = m_Device->CreateTexture2D(
"PostProcUnscaledTexture1", usage,
Renderer::Backend::Format::R8G8B8A8_UNORM,
m_UnscaledWidth, m_UnscaledHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
m_UnscaledTexture2 = m_Device->CreateTexture2D(
"PostProcUnscaledTexture2", usage,
Renderer::Backend::Format::R8G8B8A8_UNORM, m_UnscaledWidth, m_UnscaledHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.texture = m_UnscaledTexture1.get();
m_UnscaledFramebuffer1 = m_Device->CreateFramebuffer("PostprocUnscaledFramebuffer1",
&colorAttachment, nullptr);
colorAttachment.texture = m_UnscaledTexture2.get();
m_UnscaledFramebuffer2 = m_Device->CreateFramebuffer("PostprocUnscaledFramebuffer2",
&colorAttachment, nullptr);
}
// Textures for several blur sizes. It would be possible to reuse
// m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given
// that these are fairly small it's probably not worth complicating the coordinates passed
@ -386,13 +426,129 @@ Renderer::Backend::IFramebuffer* CPostprocManager::PrepareAndGetOutputFramebuffe
{
ENSURE(m_IsInitialized);
// Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point.
// Leaves m_PingFramebuffer selected for rendering; m_WhichBuffer stays true at this point.
m_WhichBuffer = true;
return m_UsingMultisampleBuffer ? m_MultisampleFramebuffer.get() : m_CaptureFramebuffer.get();
}
void CPostprocManager::UpscaleTextureByCompute(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination)
{
Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
const std::array<float, 4> screenSize{{
static_cast<float>(m_Width), static_cast<float>(m_Height),
static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)}};
constexpr uint32_t threadGroupWorkRegionDim = 16;
const uint32_t dispatchGroupCountX = DivideRoundUp(m_UnscaledWidth, threadGroupWorkRegionDim);
const uint32_t dispatchGroupCountY = DivideRoundUp(m_UnscaledHeight, threadGroupWorkRegionDim);
deviceCommandContext->BeginComputePass();
deviceCommandContext->SetComputePipelineState(
shaderTechnique->GetComputePipelineState());
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
deviceCommandContext->SetStorageTexture(shaderProgram->GetBindingSlot(str_outTex), destination);
deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
deviceCommandContext->EndComputePass();
}
void CPostprocManager::UpscaleTextureByFullscreenQuad(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::IFramebuffer* destination)
{
Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
const std::array<float, 4> screenSize{{
static_cast<float>(m_Width), static_cast<float>(m_Height),
static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)}};
deviceCommandContext->BeginFramebufferPass(destination);
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
viewportRect.width = destination->GetWidth();
viewportRect.height = destination->GetHeight();
deviceCommandContext->SetViewports(1, &viewportRect);
deviceCommandContext->SetGraphicsPipelineState(
shaderTechnique->GetGraphicsPipelineState());
deviceCommandContext->BeginPass();
deviceCommandContext->SetTexture(
shaderProgram->GetBindingSlot(str_inTex), source);
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
deviceCommandContext->EndPass();
deviceCommandContext->EndFramebufferPass();
}
void CPostprocManager::ApplySharpnessAfterScale(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination)
{
Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
// Recommended sharpness for RCAS.
constexpr float sharpness = 0.2f;
const std::array<float, 4> screenSize{ {
static_cast<float>(m_Width), static_cast<float>(m_Height),
static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)} };
constexpr uint32_t threadGroupWorkRegionDim = 16;
const uint32_t dispatchGroupCountX = DivideRoundUp(m_UnscaledWidth, threadGroupWorkRegionDim);
const uint32_t dispatchGroupCountY = DivideRoundUp(m_UnscaledHeight, threadGroupWorkRegionDim);
deviceCommandContext->BeginComputePass();
deviceCommandContext->SetComputePipelineState(
shaderTechnique->GetComputePipelineState());
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_sharpness), sharpness);
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
deviceCommandContext->SetStorageTexture(
shaderProgram->GetBindingSlot(str_outTex), destination);
deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
deviceCommandContext->EndComputePass();
}
void CPostprocManager::DownscaleTextureByCompute(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination)
{
Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
const std::array<float, 4> screenSize{{
static_cast<float>(m_Width), static_cast<float>(m_Height),
static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)}};
constexpr uint32_t threadGroupWorkRegionDim = 8;
const uint32_t dispatchGroupCountX = DivideRoundUp(m_UnscaledWidth, threadGroupWorkRegionDim);
const uint32_t dispatchGroupCountY = DivideRoundUp(m_UnscaledHeight, threadGroupWorkRegionDim);
deviceCommandContext->BeginComputePass();
deviceCommandContext->SetComputePipelineState(
shaderTechnique->GetComputePipelineState());
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
deviceCommandContext->SetStorageTexture(shaderProgram->GetBindingSlot(str_outTex), destination);
deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
deviceCommandContext->EndComputePass();
}
void CPostprocManager::BlitOutputFramebuffer(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::IFramebuffer* destination)
@ -401,24 +557,81 @@ void CPostprocManager::BlitOutputFramebuffer(
GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer");
Renderer::Backend::IFramebuffer* source =
(m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get();
Renderer::Backend::ITexture* previousTexture =
(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
// We blit to the backbuffer from the previous active buffer.
// We'll have upscaling/downscaling separately.
Renderer::Backend::IDeviceCommandContext::Rect region{};
region.width = std::min(source->GetWidth(), destination->GetWidth());
region.height = std::min(source->GetHeight(), destination->GetHeight());
deviceCommandContext->BlitFramebuffer(
source, destination, region, region,
Renderer::Backend::Sampler::Filter::NEAREST);
if (ShouldUpscale())
{
if (m_UpscaleComputeTech)
{
Renderer::Backend::ITexture* unscaledTexture = m_RCASComputeTech ? m_UnscaledTexture1.get() : m_UnscaledTexture2.get();
UpscaleTextureByCompute(deviceCommandContext, m_UpscaleComputeTech.get(), previousTexture, unscaledTexture);
if (m_RCASComputeTech)
ApplySharpnessAfterScale(deviceCommandContext, m_RCASComputeTech.get(), m_UnscaledTexture1.get(), m_UnscaledTexture2.get());
Renderer::Backend::IDeviceCommandContext::Rect sourceRegion{}, destinationRegion{};
sourceRegion.width = m_UnscaledTexture2->GetWidth();
sourceRegion.height = m_UnscaledTexture2->GetHeight();
destinationRegion.width = destination->GetWidth();
destinationRegion.height = destination->GetHeight();
deviceCommandContext->BlitFramebuffer(
m_UnscaledFramebuffer2.get(), destination, sourceRegion, destinationRegion,
Renderer::Backend::Sampler::Filter::NEAREST);
}
else
{
UpscaleTextureByFullscreenQuad(deviceCommandContext, m_UpscaleTech.get(), previousTexture, destination);
}
}
else if (ShouldDownscale())
{
Renderer::Backend::IDeviceCommandContext::Rect sourceRegion{};
Renderer::Backend::Sampler::Filter samplerFilter{
Renderer::Backend::Sampler::Filter::NEAREST};
Renderer::Backend::IFramebuffer* source{nullptr};
if (m_DownscaleComputeTech)
{
DownscaleTextureByCompute(deviceCommandContext, m_DownscaleComputeTech.get(), previousTexture, m_UnscaledTexture1.get());
source = m_UnscaledFramebuffer1.get();
sourceRegion.width = m_UnscaledTexture1->GetWidth();
sourceRegion.height = m_UnscaledTexture1->GetHeight();
}
else
{
source = (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get();
sourceRegion.width = source->GetWidth();
sourceRegion.height = source->GetHeight();
samplerFilter = Renderer::Backend::Sampler::Filter::LINEAR;
}
Renderer::Backend::IDeviceCommandContext::Rect destinationRegion{};
destinationRegion.width = destination->GetWidth();
destinationRegion.height = destination->GetHeight();
deviceCommandContext->BlitFramebuffer(
source, destination, sourceRegion, destinationRegion, samplerFilter);
}
else
{
Renderer::Backend::IFramebuffer* source =
(m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get();
// We blit to the backbuffer from the previous active buffer.
Renderer::Backend::IDeviceCommandContext::Rect region{};
region.width = std::min(source->GetWidth(), destination->GetWidth());
region.height = std::min(source->GetHeight(), destination->GetHeight());
deviceCommandContext->BlitFramebuffer(
source, destination, region, region,
Renderer::Backend::Sampler::Filter::NEAREST);
}
}
void CPostprocManager::ApplyEffect(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderTechniquePtr& shaderTech, int pass)
{
// select the other FBO for rendering
// Select the other framebuffer for rendering.
Renderer::Backend::IFramebuffer* framebuffer =
(m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get();
deviceCommandContext->BeginFramebufferPass(framebuffer);
@ -433,7 +646,7 @@ void CPostprocManager::ApplyEffect(
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(pass);
// Use the textures from the current FBO as input to the shader.
// Use the textures from the current framebuffer as input to the shader.
// We also bind a bunch of other textures and parameters, but since
// this only happens once per frame the overhead is negligible.
deviceCommandContext->SetTexture(
@ -499,7 +712,7 @@ void CPostprocManager::ApplyPostproc(
ApplyEffect(deviceCommandContext, m_AATech, pass);
}
if (hasSharp)
if (hasSharp && !ShouldUpscale())
{
for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_SharpTech, pass);
@ -617,6 +830,26 @@ void CPostprocManager::UpdateSharpnessFactor()
CFG_GET_VAL("sharpness", m_Sharpness);
}
void CPostprocManager::SetUpscaleTechnique(const CStr& upscaleName)
{
m_UpscaleTech.reset();
m_UpscaleComputeTech.reset();
m_RCASComputeTech.reset();
if (m_Device->GetCapabilities().computeShaders && upscaleName == "fsr")
{
m_UpscaleComputeTech = g_Renderer.GetShaderManager().LoadEffect(str_compute_upscale_fsr);
m_RCASComputeTech = g_Renderer.GetShaderManager().LoadEffect(str_compute_rcas);
}
else if (upscaleName == "pixelated")
{
m_UpscaleTech = g_Renderer.GetShaderManager().LoadEffect(str_upscale_nearest);
}
else
{
m_UpscaleTech = g_Renderer.GetShaderManager().LoadEffect(str_upscale_bilinear);
}
}
void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane)
{
m_NearPlane = nearPlane;
@ -695,3 +928,32 @@ void CPostprocManager::ResolveMultisampleFramebuffer(
deviceCommandContext->ResolveFramebuffer(
m_MultisampleFramebuffer.get(), m_PingFramebuffer.get());
}
void CPostprocManager::RecalculateSize(const uint32_t width, const uint32_t height)
{
if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
{
m_Scale = 1.0f;
return;
}
CFG_GET_VAL("renderer.scale", m_Scale);
if (m_Scale < 0.25f || m_Scale > 2.0f)
{
LOGWARNING("Invalid renderer scale: %0.2f", m_Scale);
m_Scale = 1.0f;
}
m_UnscaledWidth = width;
m_UnscaledHeight = height;
m_Width = m_UnscaledWidth * m_Scale;
m_Height = m_UnscaledHeight * m_Scale;
}
bool CPostprocManager::ShouldUpscale() const
{
return m_Width < m_UnscaledWidth;
}
bool CPostprocManager::ShouldDownscale() const
{
return m_Width > m_UnscaledWidth;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -56,10 +56,11 @@ public:
// Sets the current effect.
void SetPostEffect(const CStrW& name);
// Triggers update of shaders and FBO if needed.
// Triggers update of shaders and framebuffers if needed.
void UpdateAntiAliasingTechnique();
void UpdateSharpeningTechnique();
void UpdateSharpnessFactor();
void SetUpscaleTechnique(const CStr& upscaleName);
void SetDepthBufferClipPlanes(float nearPlane, float farPlane);
@ -91,6 +92,34 @@ private:
void CreateMultisampleBuffer();
void DestroyMultisampleBuffer();
void RecalculateSize(const uint32_t width, const uint32_t height);
bool ShouldUpscale() const;
bool ShouldDownscale() const;
void UpscaleTextureByCompute(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination);
void UpscaleTextureByFullscreenQuad(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::IFramebuffer* destination);
void ApplySharpnessAfterScale(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination);
void DownscaleTextureByCompute(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CShaderTechnique* shaderTechnique,
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination);
Renderer::Backend::IDevice* m_Device = nullptr;
std::unique_ptr<Renderer::Backend::IFramebuffer> m_CaptureFramebuffer;
@ -102,6 +131,12 @@ private:
// Unique color textures for the framebuffers.
std::unique_ptr<Renderer::Backend::ITexture> m_ColorTex1, m_ColorTex2;
std::unique_ptr<Renderer::Backend::ITexture>
m_UnscaledTexture1, m_UnscaledTexture2;
std::unique_ptr<Renderer::Backend::IFramebuffer>
m_UnscaledFramebuffer1, m_UnscaledFramebuffer2;
float m_Scale = 1.0f;
// The framebuffers share a depth/stencil texture.
std::unique_ptr<Renderer::Backend::ITexture> m_DepthTex;
float m_NearPlane, m_FarPlane;
@ -132,6 +167,12 @@ private:
CShaderTechniquePtr m_SharpTech;
float m_Sharpness;
CShaderTechniquePtr m_UpscaleTech;
CShaderTechniquePtr m_UpscaleComputeTech;
CShaderTechniquePtr m_DownscaleComputeTech;
// Sharp technique only for FSR upscale.
CShaderTechniquePtr m_RCASComputeTech;
CStr m_AAName;
CShaderTechniquePtr m_AATech;
bool m_UsingMultisampleBuffer;
@ -142,7 +183,8 @@ private:
std::vector<uint32_t> m_AllowedSampleCounts;
// The current screen dimensions in pixels.
int m_Width, m_Height;
uint32_t m_Width, m_Height;
uint32_t m_UnscaledWidth, m_UnscaledHeight;
// Is the postproc manager initialized? Buffers created? Default effect loaded?
bool m_IsInitialized;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -493,6 +493,7 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
g_Game->GetView()->Prepare(m->deviceCommandContext.get());
Renderer::Backend::IFramebuffer* framebuffer = nullptr;
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
CPostprocManager& postprocManager = GetPostprocManager();
if (postprocManager.IsEnabled())
@ -505,6 +506,8 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
);
postprocManager.Initialize();
framebuffer = postprocManager.PrepareAndGetOutputFramebuffer();
viewportRect.width = framebuffer->GetWidth();
viewportRect.height = framebuffer->GetHeight();
}
else
{
@ -516,13 +519,12 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::CLEAR,
Renderer::Backend::AttachmentStoreOp::DONT_CARE);
viewportRect.width = m_Width;
viewportRect.height = m_Height;
}
m->deviceCommandContext->BeginFramebufferPass(framebuffer);
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
viewportRect.width = m_Width;
viewportRect.height = m_Height;
m->deviceCommandContext->SetViewports(1, &viewportRect);
g_Game->GetView()->Render(m->deviceCommandContext.get());

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -197,6 +197,20 @@ void CRenderingOptions::ReadConfigAndSetupHooks()
g_Renderer.GetPostprocManager().UpdateSharpeningTechnique();
});
m_ConfigHooks->Setup("renderer.scale", []()
{
if (CRenderer::IsInitialised())
g_Renderer.GetPostprocManager().Resize();
});
m_ConfigHooks->Setup("renderer.upscale.technique", []()
{
CStr upscaleName;
CFG_GET_VAL("renderer.upscale.technique", upscaleName);
if (CRenderer::IsInitialised())
g_Renderer.GetPostprocManager().SetUpscaleTechnique(upscaleName);
});
m_ConfigHooks->Setup("smoothlos", m_SmoothLOS);
m_ConfigHooks->Setup("watereffects", [this]() {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -83,6 +83,13 @@ public:
virtual std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineStateDesc) = 0;
/**
* Creates a compute pipeline state. It's a caller responsibility to
* guarantee a lifespan of IShaderProgram stored in the description.
*/
virtual std::unique_ptr<IComputePipelineState> CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc) = 0;
/**
* Creates a vertex input layout. It's recommended to use as few different
* layouts as posible.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -47,6 +47,12 @@ public:
*/
virtual void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) = 0;
/**
* Binds the graphics pipeline state. It should be called only inside a
* framebuffer pass and as rarely as possible.
*/
virtual void SetComputePipelineState(IComputePipelineState* pipelineState) = 0;
// TODO: maybe we should add a more common type, like CRectI.
struct Rect
{
@ -159,8 +165,35 @@ public:
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end) = 0;
/**
* Starts a compute pass, can't be called inside a framebuffer pass.
* It should be called as rarely as possible.
*/
virtual void BeginComputePass() = 0;
/**
* Finishes a compute pass.
*/
virtual void EndComputePass() = 0;
/**
* Dispatches groupCountX * groupCountY * groupCountZ compute groups.
*/
virtual void Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ) = 0;
/**
* Sets a read-only texture to the binding slot.
*/
virtual void SetTexture(const int32_t bindingSlot, ITexture* texture) = 0;
/**
* Sets a read & write resource to the binding slot.
*/
virtual void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) = 0;
virtual void SetUniform(
const int32_t bindingSlot,
const float value) = 0;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -49,6 +49,7 @@ public:
static constexpr uint32_t SAMPLED = 1u << 2u;
static constexpr uint32_t COLOR_ATTACHMENT = 1u << 3u;
static constexpr uint32_t DEPTH_STENCIL_ATTACHMENT = 1u << 4u;
static constexpr uint32_t STORAGE = 1u << 5u;
};
virtual Type GetType() const = 0;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -171,6 +171,13 @@ struct SGraphicsPipelineStateDesc
SRasterizationStateDesc rasterizationState;
};
struct SComputePipelineStateDesc
{
// It's a backend client reponsibility to keep the shader program alive
// while it's bound.
IShaderProgram* shaderProgram;
};
// We don't provide additional helpers intentionally because all custom states
// should be described with a related shader and should be switched together.
SGraphicsPipelineStateDesc MakeDefaultGraphicsPipelineStateDesc();
@ -193,6 +200,15 @@ public:
virtual IShaderProgram* GetShaderProgram() const = 0;
};
/**
* A holder for precompiled compute pipeline description.
*/
class IComputePipelineState : public IDeviceObject<IComputePipelineState>
{
public:
virtual IShaderProgram* GetShaderProgram() const = 0;
};
} // namespace Backend
} // namespace Renderer

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -80,6 +80,12 @@ std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
return CGraphicsPipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IComputePipelineState> CDevice::CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc)
{
return CComputePipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IVertexInputLayout> CDevice::CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> UNUSED(attributes))
{

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -58,6 +58,9 @@ public:
std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineStateDesc) override;
std::unique_ptr<IComputePipelineState> CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc) override;
std::unique_ptr<IVertexInputLayout> CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> attributes) override;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -56,6 +56,11 @@ void CDeviceCommandContext::SetGraphicsPipelineState(
{
}
void CDeviceCommandContext::SetComputePipelineState(
IComputePipelineState*)
{
}
void CDeviceCommandContext::UploadTexture(
ITexture*, const Format, const void*, const size_t,
const uint32_t, const uint32_t)
@ -185,10 +190,26 @@ void CDeviceCommandContext::DrawIndexedInRange(
{
}
void CDeviceCommandContext::BeginComputePass()
{
}
void CDeviceCommandContext::EndComputePass()
{
}
void CDeviceCommandContext::Dispatch(const uint32_t, const uint32_t, const uint32_t)
{
}
void CDeviceCommandContext::SetTexture(const int32_t, ITexture*)
{
}
void CDeviceCommandContext::SetStorageTexture(const int32_t, ITexture*)
{
}
void CDeviceCommandContext::SetUniform(const int32_t, const float)
{
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -47,6 +47,7 @@ public:
IDevice* GetDevice() override;
void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override;
void SetComputePipelineState(IComputePipelineState* pipelineState) override;
void BlitFramebuffer(
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
@ -111,8 +112,18 @@ public:
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end) override;
void BeginComputePass() override;
void EndComputePass() override;
void Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ) override;
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
void SetUniform(
const int32_t bindingSlot,
const float value) override;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -45,6 +45,21 @@ IDevice* CGraphicsPipelineState::GetDevice()
return m_Device;
}
// static
std::unique_ptr<CComputePipelineState> CComputePipelineState::Create(
CDevice* device, const SComputePipelineStateDesc& desc)
{
std::unique_ptr<CComputePipelineState> pipelineState{new CComputePipelineState()};
pipelineState->m_Device = device;
pipelineState->m_Desc = desc;
return pipelineState;
}
IDevice* CComputePipelineState::GetDevice()
{
return m_Device;
}
} // namespace Dummy
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -58,6 +58,28 @@ private:
SGraphicsPipelineStateDesc m_Desc{};
};
class CComputePipelineState final : public IComputePipelineState
{
public:
~CComputePipelineState() override = default;
IDevice* GetDevice() override;
IShaderProgram* GetShaderProgram() const override { return m_Desc.shaderProgram; }
private:
friend class CDevice;
static std::unique_ptr<CComputePipelineState> Create(
CDevice* device, const SComputePipelineStateDesc& desc);
CComputePipelineState() = default;
CDevice* m_Device = nullptr;
SComputePipelineStateDesc m_Desc{};
};
} // namespace Dummy
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -362,7 +362,7 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
capabilities.ARBShaders = !ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr);
if (capabilities.ARBShaders)
capabilities.ARBShadersShadow = ogl_HaveExtension("GL_ARB_fragment_program_shadow");
capabilities.computeShaders = ogl_HaveVersion(4, 3) || ogl_HaveExtension("GL_ARB_compute_shader");
capabilities.computeShaders = ogl_HaveVersion(4, 3) || (ogl_HaveVersion(4, 2) && ogl_HaveExtension("GL_ARB_compute_shader") && ogl_HaveExtension("GL_ARB_shader_image_load_store"));
#if CONFIG2_GLES
// Some GLES implementations have GL_EXT_texture_compression_dxt1
// but that only supports DXT1 so we can't use it.
@ -865,6 +865,12 @@ std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
return CGraphicsPipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IComputePipelineState> CDevice::CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc)
{
return CComputePipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IVertexInputLayout> CDevice::CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> attributes)
{

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -71,6 +71,9 @@ public:
std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineStateDesc) override;
std::unique_ptr<IComputePipelineState> CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc) override;
std::unique_ptr<IVertexInputLayout> CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> attributes) override;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -257,17 +257,43 @@ IDevice* CDeviceCommandContext::GetDevice()
void CDeviceCommandContext::SetGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineState)
{
ENSURE(!pipelineState.shaderProgram || m_InsideFramebufferPass);
SetGraphicsPipelineStateImpl(pipelineState, false);
}
void CDeviceCommandContext::SetGraphicsPipelineState(
IGraphicsPipelineState* pipelineState)
{
ENSURE(!pipelineState->GetShaderProgram() || m_InsideFramebufferPass);
ENSURE(pipelineState);
SetGraphicsPipelineStateImpl(
pipelineState->As<CGraphicsPipelineState>()->GetDesc(), false);
}
void CDeviceCommandContext::SetComputePipelineState(
IComputePipelineState* pipelineState)
{
ENSURE(m_InsideComputePass);
ENSURE(pipelineState);
const SComputePipelineStateDesc& desc = pipelineState->As<CComputePipelineState>()->GetDesc();
if (m_ComputePipelineStateDesc.shaderProgram != desc.shaderProgram)
{
CShaderProgram* currentShaderProgram = nullptr;
if (m_ComputePipelineStateDesc.shaderProgram)
currentShaderProgram = m_ComputePipelineStateDesc.shaderProgram->As<CShaderProgram>();
CShaderProgram* nextShaderProgram = nullptr;
if (desc.shaderProgram)
nextShaderProgram = desc.shaderProgram->As<CShaderProgram>();
if (nextShaderProgram)
nextShaderProgram->Bind(currentShaderProgram);
else if (currentShaderProgram)
currentShaderProgram->Unbind();
m_ShaderProgram = nextShaderProgram;
}
}
void CDeviceCommandContext::UploadTexture(
ITexture* texture, const Format format,
const void* data, const size_t dataSize,
@ -529,6 +555,8 @@ void CDeviceCommandContext::OnTextureDestroy(CTexture* texture)
void CDeviceCommandContext::Flush()
{
ENSURE(m_ScopedLabelDepth == 0);
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
GPU_SCOPED_LABEL(this, "CDeviceCommandContext::Flush");
@ -952,6 +980,8 @@ void CDeviceCommandContext::EndFramebufferPass()
ogl_WarnIfError();
}
m_Framebuffer = framebuffer;
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), false);
}
void CDeviceCommandContext::ReadbackFramebufferSync(
@ -1209,6 +1239,31 @@ void CDeviceCommandContext::DrawIndexedInRange(
ogl_WarnIfError();
}
void CDeviceCommandContext::BeginComputePass()
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
m_InsideComputePass = true;
}
void CDeviceCommandContext::EndComputePass()
{
ENSURE(m_InsideComputePass);
m_InsideComputePass = false;
}
void CDeviceCommandContext::Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ)
{
ENSURE(m_InsideComputePass);
glDispatchCompute(groupCountX, groupCountY, groupCountZ);
// TODO: we might want to do binding tracking to avoid redundant barriers.
glMemoryBarrier(
GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
}
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
{
ENSURE(m_ShaderProgram);
@ -1251,6 +1306,21 @@ void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* text
BindTexture(unit, textureUnit.target, texture->As<CTexture>()->GetHandle());
}
void CDeviceCommandContext::SetStorageTexture(const int32_t bindingSlot, ITexture* texture)
{
ENSURE(m_ShaderProgram);
ENSURE(texture);
ENSURE(texture->GetUsage() & Renderer::Backend::ITexture::Usage::STORAGE);
const CShaderProgram::TextureUnit textureUnit =
m_ShaderProgram->GetTextureUnit(bindingSlot);
if (!textureUnit.type)
return;
ENSURE(textureUnit.type == GL_IMAGE_2D);
ENSURE(texture->GetFormat() == Format::R8G8B8A8_UNORM);
glBindImageTexture(textureUnit.unit, texture->As<CTexture>()->GetHandle(), 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float value)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -56,6 +56,7 @@ public:
void SetGraphicsPipelineState(const SGraphicsPipelineStateDesc& pipelineState);
void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override;
void SetComputePipelineState(IComputePipelineState* pipelineState) override;
void BlitFramebuffer(
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
@ -120,8 +121,18 @@ public:
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end) override;
void BeginComputePass() override;
void EndComputePass() override;
void Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ) override;
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
void SetUniform(
const int32_t bindingSlot,
const float value) override;
@ -172,6 +183,8 @@ private:
// GL2.1 doesn't support more than 1 scissor.
std::array<Rect, 1> m_Scissors;
SComputePipelineStateDesc m_ComputePipelineStateDesc{};
uint32_t m_ScopedLabelDepth = 0;
CBuffer* m_VertexBuffer = nullptr;
@ -180,6 +193,7 @@ private:
bool m_InsideFramebufferPass = false;
bool m_InsidePass = false;
bool m_InsideComputePass = false;
uint32_t m_ActiveTextureUnit = 0;
struct BindUnit

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -45,6 +45,21 @@ IDevice* CGraphicsPipelineState::GetDevice()
return m_Device;
}
// static
std::unique_ptr<CComputePipelineState> CComputePipelineState::Create(
CDevice* device, const SComputePipelineStateDesc& desc)
{
std::unique_ptr<CComputePipelineState> pipelineState{new CComputePipelineState()};
pipelineState->m_Device = device;
pipelineState->m_Desc = desc;
return pipelineState;
}
IDevice* CComputePipelineState::GetDevice()
{
return m_Device;
}
} // namespace GL
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -59,6 +59,30 @@ private:
SGraphicsPipelineStateDesc m_Desc{};
};
class CComputePipelineState final : public IComputePipelineState
{
public:
~CComputePipelineState() override = default;
IDevice* GetDevice() override;
IShaderProgram* GetShaderProgram() const override { return m_Desc.shaderProgram; }
const SComputePipelineStateDesc& GetDesc() const { return m_Desc; }
private:
friend class CDevice;
static std::unique_ptr<CComputePipelineState> Create(
CDevice* device, const SComputePipelineStateDesc& desc);
CComputePipelineState() = default;
CDevice* m_Device = nullptr;
SComputePipelineStateDesc m_Desc{};
};
} // namespace GL
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -647,7 +647,10 @@ public:
m_Program = 0;
m_FileDependencies = {programPath};
for (const auto& [path, type] : shaderStages)
{
UNUSED2(type);
m_FileDependencies.emplace_back(path);
}
// TODO: replace by scoped bind.
m_Device->GetActiveCommandContext()->SetGraphicsPipelineState(
@ -672,6 +675,9 @@ public:
case GL_FRAGMENT_SHADER:
stageDefine = "STAGE_FRAGMENT";
break;
case GL_COMPUTE_SHADER:
stageDefine = "STAGE_COMPUTE";
break;
default:
break;
}
@ -845,18 +851,35 @@ public:
#undef CASE
// Assign sampler uniforms to sequential texture units.
if (type == GL_SAMPLER_2D
|| type == GL_SAMPLER_CUBE
switch (type)
{
case GL_SAMPLER_2D:
bindingSlot.elementType = GL_TEXTURE_2D;
bindingSlot.isTexture = true;
break;
case GL_SAMPLER_CUBE:
bindingSlot.elementType = GL_TEXTURE_CUBE_MAP;
bindingSlot.isTexture = true;
break;
#if !CONFIG2_GLES
|| type == GL_SAMPLER_2D_SHADOW
case GL_SAMPLER_2D_SHADOW:
bindingSlot.elementType = GL_TEXTURE_2D;
bindingSlot.isTexture = true;
break;
case GL_IMAGE_2D:
bindingSlot.elementType = GL_IMAGE_2D;
bindingSlot.isTexture = true;
break;
#endif
)
default:
break;
}
if (bindingSlot.isTexture)
{
const auto it = requiredUnits.find(nameIntern);
const int unit = it == requiredUnits.end() ? -1 : it->second;
bindingSlot.elementType = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D);
bindingSlot.elementCount = unit;
bindingSlot.isTexture = true;
if (unit != -1)
{
if (unit >= static_cast<int>(occupiedUnits.size()))
@ -1192,6 +1215,7 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(CDevice* device, const CS
// Define all the elements and attributes used in the XML file
#define EL(x) int el_##x = XeroFile.GetElementID(#x)
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
EL(compute);
EL(define);
EL(fragment);
EL(stream);
@ -1222,6 +1246,8 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(CDevice* device, const CS
std::map<CStrIntern, int> vertexAttribs;
int streamFlags = 0;
VfsPath computeFile;
XERO_ITER_EL(root, child)
{
if (child.GetNodeName() == el_define)
@ -1307,12 +1333,22 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(CDevice* device, const CS
}
}
}
else if (child.GetNodeName() == el_compute)
{
computeFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8();
}
}
if (isGLSL)
{
const std::array<std::tuple<VfsPath, GLenum>, 2> shaderStages{{
{vertexFile, GL_VERTEX_SHADER}, {fragmentFile, GL_FRAGMENT_SHADER}}};
if (!computeFile.empty())
{
ENSURE(streamFlags == 0);
ENSURE(vertexAttribs.empty());
}
const PS::StaticVector<std::tuple<VfsPath, GLenum>, 2> shaderStages{computeFile.empty()
? PS::StaticVector<std::tuple<VfsPath, GLenum>, 2>{{vertexFile, GL_VERTEX_SHADER}, {fragmentFile, GL_FRAGMENT_SHADER}}
: PS::StaticVector<std::tuple<VfsPath, GLenum>, 2>{{computeFile, GL_COMPUTE_SHADER}}};
return std::make_unique<CShaderProgramGLSL>(
device, name, xmlFilename, shaderStages, defines,
vertexAttribs, streamFlags);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -192,6 +192,8 @@ public:
VkDescriptorSetLayout GetDescriptorSetLayout() { return m_DescriptorSetLayout; }
const std::vector<DeviceObject*>& GetBoundDeviceObjects() const { return m_BoundDeviceObjects; }
private:
CDevice* const m_Device;
const VkDescriptorType m_Type;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -708,6 +708,12 @@ std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
return CGraphicsPipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IComputePipelineState> CDevice::CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc)
{
return CComputePipelineState::Create(this, pipelineStateDesc);
}
std::unique_ptr<IVertexInputLayout> CDevice::CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> attributes)
{

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -80,6 +80,9 @@ public:
std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineStateDesc) override;
std::unique_ptr<IComputePipelineState> CreateComputePipelineState(
const SComputePipelineStateDesc& pipelineStateDesc) override;
std::unique_ptr<IVertexInputLayout> CreateVertexInputLayout(
const PS::span<const SVertexAttributeFormat> attributes) override;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -68,7 +68,7 @@ SBaseImageState GetBaseImageState(CTexture* texture)
return {
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT};
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT};
}
else if (texture->GetUsage() & ITexture::Usage::COLOR_ATTACHMENT)
{
@ -279,7 +279,7 @@ void CDeviceCommandContext::CUploadRing::ExecuteUploads(
const VkPipelineStageFlags stageMask =
m_Type == IBuffer::Type::UNIFORM
? VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
? VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
: VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
Utilities::SubmitBufferMemoryBarrier(
@ -407,6 +407,28 @@ void CDeviceCommandContext::SetGraphicsPipelineState(
m_IsPipelineStateDirty = true;
}
void CDeviceCommandContext::SetComputePipelineState(
IComputePipelineState* pipelineState)
{
if (m_ShaderProgram)
m_ShaderProgram->Unbind();
ENSURE(pipelineState);
CComputePipelineState* computePipelineState = pipelineState->As<CComputePipelineState>();
m_ShaderProgram = computePipelineState->GetShaderProgram()->As<CShaderProgram>();
m_ShaderProgram->Bind();
vkCmdBindPipeline(
m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(), computePipelineState->GetPipeline());
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::BlitFramebuffer(
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
const Rect& sourceRegion, const Rect& destinationRegion,
@ -554,6 +576,8 @@ void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth,
void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
ENSURE(framebuffer);
m_IsPipelineStateDirty = true;
m_Framebuffer = framebuffer->As<CFramebuffer>();
@ -575,7 +599,7 @@ void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer)
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);
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
}
CTexture* depthStencilAttachment = m_Framebuffer->GetDepthStencilAttachment();
@ -589,7 +613,7 @@ void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer)
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_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT);
}
@ -653,7 +677,7 @@ void CDeviceCommandContext::EndFramebufferPass()
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);
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
CTexture* depthStencilAttachment = m_Framebuffer->GetDepthStencilAttachment();
@ -666,7 +690,7 @@ void CDeviceCommandContext::EndFramebufferPass()
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);
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
m_LastBoundPipeline = VK_NULL_HANDLE;
@ -891,17 +915,49 @@ void CDeviceCommandContext::DrawIndexedInRange(
DrawIndexed(firstIndex, indexCount, 0);
}
void CDeviceCommandContext::BeginComputePass()
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
m_InsideComputePass = true;
}
void CDeviceCommandContext::EndComputePass()
{
if (m_ShaderProgram)
{
m_ShaderProgram->Unbind();
m_ShaderProgram = nullptr;
}
ENSURE(m_InsideComputePass);
m_InsideComputePass = false;
}
void CDeviceCommandContext::Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ)
{
ENSURE(m_InsideComputePass);
m_ShaderProgram->PreDispatch(*m_CommandContext);
UpdateOutdatedConstants();
vkCmdDispatch(
m_CommandContext->GetCommandBuffer(), groupCountX, groupCountY, groupCountZ);
m_ShaderProgram->PostDispatch(*m_CommandContext);
}
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
{
if (bindingSlot < 0)
return;
ENSURE(m_InsidePass);
ENSURE(m_InsidePass || m_InsideComputePass);
ENSURE(texture);
CTexture* textureToBind = texture->As<CTexture>();
ENSURE(textureToBind->GetUsage() & ITexture::Usage::SAMPLED);
if (!m_Device->GetDescriptorManager().UseDescriptorIndexing())
if (!m_Device->GetDescriptorManager().UseDescriptorIndexing() && m_InsidePass)
{
// We can't bind textures which are used as attachments.
const auto& colorAttachments = m_Framebuffer->GetColorAttachments();
@ -915,11 +971,20 @@ void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* text
m_ShaderProgram->SetTexture(bindingSlot, textureToBind);
}
void CDeviceCommandContext::SetStorageTexture(const int32_t bindingSlot, ITexture* texture)
{
ENSURE(m_InsidePass || m_InsideComputePass);
ENSURE(texture);
CTexture* textureToBind = texture->As<CTexture>();
ENSURE(textureToBind->GetUsage() & ITexture::Usage::STORAGE);
m_ShaderProgram->SetStorageTexture(bindingSlot, textureToBind);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float value)
{
ENSURE(m_InsidePass);
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, value);
}
@ -927,7 +992,7 @@ void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY)
{
ENSURE(m_InsidePass);
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY);
}
@ -936,7 +1001,7 @@ void CDeviceCommandContext::SetUniform(
const float valueX, const float valueY,
const float valueZ)
{
ENSURE(m_InsidePass);
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ);
}
@ -945,14 +1010,14 @@ void CDeviceCommandContext::SetUniform(
const float valueX, const float valueY,
const float valueZ, const float valueW)
{
ENSURE(m_InsidePass);
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot, PS::span<const float> values)
{
ENSURE(m_InsidePass);
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, values);
}
@ -1074,6 +1139,11 @@ void CDeviceCommandContext::PreDraw()
ENSURE(m_InsidePass);
ApplyPipelineStateIfDirty();
m_ShaderProgram->PreDraw(*m_CommandContext);
UpdateOutdatedConstants();
}
void CDeviceCommandContext::UpdateOutdatedConstants()
{
if (m_ShaderProgram->IsMaterialConstantsDataOutdated())
{
const VkDeviceSize alignment =

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -51,6 +51,7 @@ public:
IDevice* GetDevice() override;
void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override;
void SetComputePipelineState(IComputePipelineState* pipelineState) override;
void BlitFramebuffer(
IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer,
@ -114,8 +115,18 @@ public:
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end) override;
void BeginComputePass() override;
void EndComputePass() override;
void Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ) override;
void SetTexture(const int32_t bindingSlot, ITexture* texture) override;
void SetStorageTexture(const int32_t bindingSlot, ITexture* texture) override;
void SetUniform(
const int32_t bindingSlot,
const float value) override;
@ -146,6 +157,7 @@ private:
CDeviceCommandContext();
void PreDraw();
void UpdateOutdatedConstants();
void ApplyPipelineStateIfDirty();
void BindVertexBuffer(const uint32_t bindingSlot, CBuffer* buffer, uint32_t offset);
void BindIndexBuffer(CBuffer* buffer, uint32_t offset);
@ -166,6 +178,7 @@ private:
bool m_InsideFramebufferPass = false;
bool m_InsidePass = false;
bool m_InsideComputePass = false;
// Currently bound buffers to skip the same buffer bind.
CBuffer* m_BoundIndexBuffer = nullptr;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -306,6 +306,45 @@ IDevice* CGraphicsPipelineState::GetDevice()
return m_Device;
}
// static
std::unique_ptr<CComputePipelineState> CComputePipelineState::Create(
CDevice* device, const SComputePipelineStateDesc& desc)
{
ENSURE(desc.shaderProgram);
CShaderProgram* shaderProgram = desc.shaderProgram->As<CShaderProgram>();
if (shaderProgram->GetStages().empty())
return nullptr;
std::unique_ptr<CComputePipelineState> pipelineState{new CComputePipelineState()};
pipelineState->m_Device = device;
pipelineState->m_UID = device->GenerateNextDeviceObjectUID();
pipelineState->m_Desc = desc;
VkComputePipelineCreateInfo pipelineCreateInfo{};
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
pipelineCreateInfo.layout = shaderProgram->GetPipelineLayout();
pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
pipelineCreateInfo.basePipelineIndex = -1;
pipelineCreateInfo.stage = shaderProgram->GetStages()[0];
ENSURE_VK_SUCCESS(vkCreateComputePipelines(
device->GetVkDevice(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipelineState->m_Pipeline));
return pipelineState;
}
CComputePipelineState::~CComputePipelineState()
{
if (m_Pipeline != VK_NULL_HANDLE)
m_Device->ScheduleObjectToDestroy(
VK_OBJECT_TYPE_PIPELINE, m_Pipeline, VK_NULL_HANDLE);
}
IDevice* CComputePipelineState::GetDevice()
{
return m_Device;
}
} // namespace Vulkan
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -87,6 +87,38 @@ private:
std::unordered_map<CacheKey, VkPipeline, CacheKeyHash, CacheKeyEqual> m_PipelineMap;
};
class CComputePipelineState final : public IComputePipelineState
{
public:
~CComputePipelineState() override;
IDevice* GetDevice() override;
IShaderProgram* GetShaderProgram() const override { return m_Desc.shaderProgram; }
const SComputePipelineStateDesc& GetDesc() const { return m_Desc; }
VkPipeline GetPipeline() { return m_Pipeline; }
DeviceObjectUID GetUID() const { return m_UID; }
private:
friend class CDevice;
static std::unique_ptr<CComputePipelineState> Create(
CDevice* device, const SComputePipelineStateDesc& desc);
CComputePipelineState() = default;
CDevice* m_Device{nullptr};
DeviceObjectUID m_UID{INVALID_DEVICE_OBJECT_UID};
SComputePipelineStateDesc m_Desc{};
VkPipeline m_Pipeline{VK_NULL_HANDLE};
};
} // namespace Vulkan
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -185,7 +185,7 @@ void CRingCommandContext::ScheduleUpload(
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);
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
VkBufferImageCopy region{};
@ -206,7 +206,7 @@ void CRingCommandContext::ScheduleUpload(
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkAccessFlags dstAccessFlags = VK_ACCESS_SHADER_READ_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
Utilities::SubmitImageMemoryBarrier(
commandBuffer, image, level, layer,
VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessFlags,
@ -259,7 +259,7 @@ void CRingCommandContext::ScheduleUpload(
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;
srcStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
Utilities::SubmitPipelineBarrier(
commandBuffer, srcStageMask, dstStageMask);
@ -293,7 +293,7 @@ void CRingCommandContext::ScheduleUpload(
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;
dstStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
}
Utilities::SubmitBufferMemoryBarrier(
commandBuffer, buffer, dataOffset, dataSize,

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -171,6 +171,7 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
#define EL(x) const int el_##x = programXeroFile.GetElementID(#x)
#define AT(x) const int at_##x = programXeroFile.GetAttributeID(#x)
EL(binding);
EL(compute);
EL(descriptor_set);
EL(descriptor_sets);
EL(fragment);
@ -231,6 +232,10 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
uint32_t texturesDescriptorSetSize = 0;
std::unordered_map<CStrIntern, uint32_t> textureMapping;
VkDescriptorType storageImageDescriptorType = VK_DESCRIPTOR_TYPE_MAX_ENUM;
uint32_t storageImageDescriptorSetSize = 0;
std::unordered_map<CStrIntern, uint32_t> storageImageMapping;
auto addDescriptorSets = [&](const XMBElement& element) -> bool
{
const bool useDescriptorIndexing =
@ -309,6 +314,23 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
texturesDescriptorSetSize =
std::max(texturesDescriptorSetSize, binding + 1);
}
else if (type == "storageImage" || type == "storageBuffer")
{
const CStrIntern name{attributes.GetNamedItem(at_name)};
storageImageMapping[name] = binding;
storageImageDescriptorSetSize =
std::max(storageImageDescriptorSetSize, binding + 1);
const VkDescriptorType descriptorType = type == "storageBuffer"
? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
if (storageImageDescriptorType == VK_DESCRIPTOR_TYPE_MAX_ENUM)
storageImageDescriptorType = descriptorType;
else if (storageImageDescriptorType != descriptorType)
{
LOGERROR("Shader should have storages of the same type.");
return false;
}
}
else
{
LOGERROR("Unsupported binding: '%s'", type.c_str());
@ -325,6 +347,13 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
{
if (programChild.GetNodeName() == el_vertex)
{
if (shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM &&
shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_GRAPHICS)
{
LOGERROR("Shader program can't mix different pipelines: '%s'.", name.c_str());
return nullptr;
}
shaderProgram->m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
const VfsPath shaderModulePath =
L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8();
shaderProgram->m_FileDependencies.emplace_back(shaderModulePath);
@ -386,6 +415,13 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
}
else if (programChild.GetNodeName() == el_fragment)
{
if (shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM &&
shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_GRAPHICS)
{
LOGERROR("Shader program can't mix different pipelines: '%s'.", name.c_str());
return nullptr;
}
shaderProgram->m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
const VfsPath shaderModulePath =
L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8();
shaderProgram->m_FileDependencies.emplace_back(shaderModulePath);
@ -413,6 +449,42 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
}
}
}
else if (programChild.GetNodeName() == el_compute)
{
if (shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM &&
shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_COMPUTE)
{
LOGERROR("Shader program can't mix different pipelines: '%s'.", name.c_str());
return nullptr;
}
shaderProgram->m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
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 computeShaderStageInfo{};
computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
computeShaderStageInfo.module = shaderProgram->m_ShaderModules.back();
computeShaderStageInfo.pName = "main";
shaderProgram->m_Stages.emplace_back(std::move(computeShaderStageInfo));
XERO_ITER_EL(programChild, stageChild)
{
if (stageChild.GetNodeName() == el_push_constant)
{
if (!addPushConstant(stageChild, VK_SHADER_STAGE_COMPUTE_BIT))
return nullptr;
}
else if (stageChild.GetNodeName() == el_descriptor_sets)
{
if (!addDescriptorSets(stageChild))
return nullptr;
}
}
}
}
if (shaderProgram->m_Stages.empty())
@ -421,6 +493,8 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
return nullptr;
}
ENSURE(shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM);
for (size_t index = 0; index < shaderProgram->m_PushConstants.size(); ++index)
shaderProgram->m_PushConstantMapping[shaderProgram->m_PushConstants[index].name] = index;
std::vector<VkPushConstantRange> pushConstantRanges;
@ -482,6 +556,12 @@ std::unique_ptr<CShaderProgram> CShaderProgram::Create(
device, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, texturesDescriptorSetSize, std::move(textureMapping));
layouts.emplace_back(shaderProgram->m_TextureBinding->GetDescriptorSetLayout());
}
if (storageImageDescriptorSetSize > 0)
{
shaderProgram->m_StorageImageBinding.emplace(
device, storageImageDescriptorType, storageImageDescriptorSetSize, std::move(storageImageMapping));
layouts.emplace_back(shaderProgram->m_StorageImageBinding->GetDescriptorSetLayout());
}
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{};
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
@ -527,6 +607,8 @@ int32_t CShaderProgram::GetBindingSlot(const CStrIntern name) const
return it->second + m_PushConstants.size();
if (const int32_t bindingSlot = m_TextureBinding.has_value() ? m_TextureBinding->GetBindingSlot(name) : -1; bindingSlot != -1)
return bindingSlot + m_PushConstants.size() + m_UniformMapping.size();
if (const int32_t bindingSlot = m_StorageImageBinding.has_value() ? m_StorageImageBinding->GetBindingSlot(name) : -1; bindingSlot != -1)
return bindingSlot + m_PushConstants.size() + m_UniformMapping.size() + (m_TextureBinding.has_value() ? m_TextureBinding->GetBoundDeviceObjects().size() : 0);
return -1;
}
@ -551,6 +633,8 @@ void CShaderProgram::Unbind()
{
if (m_TextureBinding.has_value())
m_TextureBinding->Unbind();
if (m_StorageImageBinding.has_value())
m_StorageImageBinding->Unbind();
}
void CShaderProgram::PreDraw(CRingCommandContext& commandContext)
@ -578,16 +662,63 @@ void CShaderProgram::PreDraw(CRingCommandContext& commandContext)
}
}
void CShaderProgram::PreDispatch(
CRingCommandContext& commandContext)
{
PreDraw(commandContext);
if (m_StorageImageBinding.has_value())
for (CTexture* texture : m_StorageImageBinding->GetBoundDeviceObjects())
if (texture)
{
if (!(texture->GetUsage() & ITexture::Usage::SAMPLED) && texture->IsInitialized())
continue;
VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
if (!texture->IsInitialized())
oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Utilities::SetTextureLayout(
commandContext.GetCommandBuffer(), texture,
oldLayout,
VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
}
void CShaderProgram::PostDispatch(CRingCommandContext& commandContext)
{
if (m_StorageImageBinding.has_value())
for (CTexture* texture : m_StorageImageBinding->GetBoundDeviceObjects())
if (texture)
{
if (!(texture->GetUsage() & ITexture::Usage::SAMPLED) && texture->IsInitialized())
continue;
Utilities::SetTextureLayout(
commandContext.GetCommandBuffer(), texture,
VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
}
void CShaderProgram::BindOutdatedDescriptorSets(
CRingCommandContext& commandContext)
{
// TODO: combine calls after more sets to bind.
PS::StaticVector<std::tuple<uint32_t, VkDescriptorSet>, 1> descriptortSets;
PS::StaticVector<std::tuple<uint32_t, VkDescriptorSet>, 2> descriptortSets;
if (m_TextureBinding.has_value() && m_TextureBinding->IsOutdated())
{
constexpr uint32_t TEXTURE_BINDING_SET = 1u;
descriptortSets.emplace_back(TEXTURE_BINDING_SET, m_TextureBinding->UpdateAndReturnDescriptorSet());
}
if (m_StorageImageBinding.has_value() && m_StorageImageBinding->IsOutdated())
{
constexpr uint32_t STORAGE_IMAGE_BINDING_SET = 2u;
descriptortSets.emplace_back(STORAGE_IMAGE_BINDING_SET, m_StorageImageBinding->UpdateAndReturnDescriptorSet());
}
for (const auto [firstSet, descriptorSet] : descriptortSets)
{
@ -687,6 +818,17 @@ void CShaderProgram::SetTexture(const int32_t bindingSlot, CTexture* texture)
}
}
void CShaderProgram::SetStorageTexture(const int32_t bindingSlot, CTexture* texture)
{
if (bindingSlot < 0)
return;
const int32_t offset = static_cast<int32_t>(m_PushConstants.size() + m_UniformMapping.size() + (m_TextureBinding.has_value() ? m_TextureBinding->GetBoundDeviceObjects().size() : 0));
ENSURE(bindingSlot >= offset);
ENSURE(m_StorageImageBinding.has_value());
const uint32_t index = bindingSlot - offset;
m_StorageImageBinding->SetObject(index, texture);
}
} // namespace Vulkan
} // namespace Backend

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,6 +19,7 @@
#define INCLUDED_RENDERER_BACKEND_VULKAN_SHADERPROGRAM
#include "renderer/backend/IShaderProgram.h"
#include "renderer/backend/vulkan/Buffer.h"
#include "renderer/backend/vulkan/DescriptorManager.h"
#include "renderer/backend/vulkan/Texture.h"
@ -94,10 +95,13 @@ public:
void Bind();
void Unbind();
void PreDraw(CRingCommandContext& commandContext);
void PreDispatch(CRingCommandContext& commandContext);
void PostDispatch(CRingCommandContext& commandContext);
VkPipelineLayout GetPipelineLayout() const { return m_PipelineLayout; }
VkPipelineBindPoint GetPipelineBindPoint() const { return VK_PIPELINE_BIND_POINT_GRAPHICS; }
VkPipelineBindPoint GetPipelineBindPoint() const { return m_PipelineBindPoint; }
void SetUniform(
const int32_t bindingSlot,
@ -115,7 +119,9 @@ public:
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);
void SetStorageTexture(const int32_t bindingSlot, CTexture* texture);
// TODO: rename to something related to buffer.
bool IsMaterialConstantsDataOutdated() const { return m_MaterialConstantsDataOutdated; }
@ -142,6 +148,7 @@ private:
std::vector<VkShaderModule> m_ShaderModules;
std::vector<VkPipelineShaderStageCreateInfo> m_Stages;
VkPipelineLayout m_PipelineLayout = VK_NULL_HANDLE;
VkPipelineBindPoint m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_MAX_ENUM;
std::vector<VfsPath> m_FileDependencies;
@ -170,6 +177,7 @@ private:
std::unordered_map<CStrIntern, uint32_t> m_PushConstantMapping;
std::optional<CSingleTypeDescriptorSetBinding<CTexture>> m_TextureBinding;
std::optional<CSingleTypeDescriptorSetBinding<CTexture>> m_StorageImageBinding;
std::unordered_map<VertexAttributeStream, uint32_t> m_StreamLocations;
};

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -145,9 +145,11 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
// 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.
// VK_IMAGE_USAGE_STORAGE_BIT allows to write to the backbuffer directly
// from a compute shader.
swapChainCreateInfo.imageUsage =
(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT) &
surfaceCapabilities.supportedUsageFlags;
(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_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;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -73,6 +73,15 @@ std::unique_ptr<CTexture> CTexture::Create(
vkGetPhysicalDeviceFormatProperties(
physicalDevice, imageFormat, &formatProperties);
if (!(usage & Usage::SAMPLED))
{
// A texture can't be *_ATTACHMENT and STORAGE at the same time without
// to be SAMPLED.
const bool isAttachment = (usage & Usage::COLOR_ATTACHMENT) || (usage & Usage::DEPTH_STENCIL_ATTACHMENT);
const bool isStorage = usage & Usage::STORAGE;
ENSURE(!(isAttachment && isStorage));
}
VkImageUsageFlags usageFlags = 0;
// Vulkan 1.0 implies that TRANSFER_SRC and TRANSFER_DST are supported.
// TODO: account Vulkan 1.1.
@ -90,6 +99,16 @@ std::unique_ptr<CTexture> CTexture::Create(
}
usageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (usage & Usage::STORAGE)
{
ENSURE(type != Type::TEXTURE_2D_MULTISAMPLE);
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT))
{
LOGERROR("Format %d doesn't support storage for optimal tiling.", static_cast<int>(imageFormat));
return nullptr;
}
usageFlags |= VK_IMAGE_USAGE_STORAGE_BIT;
}
if (usage & Usage::COLOR_ATTACHMENT)
{
ENSURE(device->IsFramebufferFormatSupported(format));