Index: ps/trunk/source/graphics/LOSTexture.cpp
===================================================================
--- ps/trunk/source/graphics/LOSTexture.cpp (revision 27312)
+++ ps/trunk/source/graphics/LOSTexture.cpp (revision 27313)
@@ -1,482 +1,476 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "LOSTexture.h"
#include "graphics/ShaderManager.h"
#include "lib/bits.h"
#include "lib/config2.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/backend/IDevice.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/TimeManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/helpers/Los.h"
/*
The LOS bitmap is computed with one value per LOS vertex, based on
CCmpRangeManager's visibility information.
The bitmap is then blurred using an NxN filter (in particular a
7-tap Binomial filter as an efficient integral approximation of a Gaussian).
To implement the blur efficiently without using extra memory for a second copy
of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side,
then the blur shifts the image back into the corner.
The blurred bitmap is then uploaded into a GL texture for use by the renderer.
*/
// Blur with a NxN filter, where N = g_BlurSize must be an odd number.
// Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES.
static const size_t g_BlurSize = 7;
// Alignment (in bytes) of the pixel data passed into texture uploading.
// This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since
// that's what we set it to) but in some weird cases appears to have a different
// value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway.
static const size_t g_SubTextureAlignment = 4;
CLOSTexture::CLOSTexture(CSimulation2& simulation)
: m_Simulation(simulation)
{
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
CreateShader();
}
CLOSTexture::~CLOSTexture()
{
m_SmoothFramebuffers[0].reset();
m_SmoothFramebuffers[1].reset();
if (m_Texture)
DeleteTexture();
}
// Create the LOS texture engine. Should be ran only once.
bool CLOSTexture::CreateShader()
{
m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp);
m_ShaderInitialized = m_SmoothTech && m_SmoothTech->GetShader();
if (!m_ShaderInitialized)
{
LOGERROR("Failed to load SmoothLOS shader, disabling.");
g_RenderingOptions.SetSmoothLOS(false);
return false;
}
return true;
}
void CLOSTexture::DeleteTexture()
{
m_Texture.reset();
m_SmoothTextures[0].reset();
m_SmoothTextures[1].reset();
}
void CLOSTexture::MakeDirty()
{
m_Dirty = true;
}
Renderer::Backend::ITexture* CLOSTexture::GetTextureSmooth()
{
if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS())
return GetTexture();
else
return m_SmoothTextures[m_WhichTexture].get();
}
void CLOSTexture::InterpolateLOS(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS();
if (!skipSmoothLOS && !m_ShaderInitialized)
{
if (!CreateShader())
return;
// RecomputeTexture will not cause the ConstructTexture to run.
// Force the textures to be created.
DeleteTexture();
ConstructTexture(deviceCommandContext);
m_Dirty = true;
}
if (m_Dirty)
{
RecomputeTexture(deviceCommandContext);
// We need to subtract the frame time because without it we have the
// same output images for the current and previous frames.
m_LastTextureRecomputeTime = timer_Time() - g_Renderer.GetTimeManager().GetFrameDelta();
m_WhichTexture = 1u - m_WhichTexture;
m_Dirty = false;
}
if (skipSmoothLOS)
return;
GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture");
deviceCommandContext->BeginFramebufferPass(m_SmoothFramebuffers[m_WhichTexture].get());
deviceCommandContext->SetGraphicsPipelineState(
m_SmoothTech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* shader = m_SmoothTech->GetShader();
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_losTex1), m_Texture.get());
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_losTex2), m_SmoothTextures[1u - m_WhichTexture].get());
const float delta = Clamp(
(timer_Time() - m_LastTextureRecomputeTime) * 2.0f, 0.0f, 1.0f);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_delta), delta);
- const SViewPort oldVp = g_Renderer.GetViewport();
- const SViewPort vp =
- {
- 0, 0,
- static_cast(m_Texture->GetWidth()),
- static_cast(m_Texture->GetHeight())
- };
- g_Renderer.SetViewport(vp);
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = m_Texture->GetWidth();
+ viewportRect.height = m_Texture->GetHeight();
+ deviceCommandContext->SetViewports(1, &viewportRect);
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->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1);
deviceCommandContext->SetVertexBufferData(
0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
deviceCommandContext->SetVertexBufferData(
1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
deviceCommandContext->Draw(0, 6);
- g_Renderer.SetViewport(oldVp);
-
deviceCommandContext->EndPass();
deviceCommandContext->EndFramebufferPass();
}
Renderer::Backend::ITexture* CLOSTexture::GetTexture()
{
ENSURE(!m_Dirty);
return m_Texture.get();
}
const CMatrix3D& CLOSTexture::GetTextureMatrix()
{
ENSURE(!m_Dirty);
return m_TextureMatrix;
}
const CMatrix3D& CLOSTexture::GetMinimapTextureMatrix()
{
ENSURE(!m_Dirty);
return m_MinimapTextureMatrix;
}
void CLOSTexture::ConstructTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
m_MapSize = cmpRangeManager->GetVerticesPerSide();
const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment));
Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice();
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
if (backendDevice->IsFramebufferFormatSupported(Renderer::Backend::Format::R8_UNORM))
{
m_TextureFormat = Renderer::Backend::Format::R8_UNORM;
m_TextureFormatStride = 1;
}
else
{
m_TextureFormat = Renderer::Backend::Format::R8G8B8A8_UNORM;
m_TextureFormatStride = 4;
}
m_Texture = backendDevice->CreateTexture2D("LOSTexture",
Renderer::Backend::ITexture::Usage::TRANSFER_DST |
Renderer::Backend::ITexture::Usage::SAMPLED,
m_TextureFormat, textureSize, textureSize, defaultSamplerDesc);
// Initialise texture with SoD color, for the areas we don't
// overwrite with uploading later.
const size_t textureDataSize = textureSize * textureSize * m_TextureFormatStride;
std::unique_ptr texData = std::make_unique(textureDataSize);
memset(texData.get(), 0x00, textureDataSize);
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
{
const uint32_t usage =
Renderer::Backend::ITexture::Usage::TRANSFER_DST |
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT;
m_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0",
usage, m_TextureFormat, textureSize, textureSize, defaultSamplerDesc);
m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1",
usage, m_TextureFormat, textureSize, textureSize, defaultSamplerDesc);
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = m_SmoothTextures[0].get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer(
"LOSSmoothFramebuffer0", &colorAttachment, nullptr);
colorAttachment.texture = m_SmoothTextures[1].get();
m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer(
"LOSSmoothFramebuffer1", &colorAttachment, nullptr);
if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1])
{
LOGERROR("Failed to create LOS framebuffers");
g_RenderingOptions.SetSmoothLOS(false);
}
deviceCommandContext->UploadTexture(
m_SmoothTextures[0].get(), m_TextureFormat, texData.get(), textureDataSize);
deviceCommandContext->UploadTexture(
m_SmoothTextures[1].get(), m_TextureFormat, texData.get(), textureDataSize);
}
deviceCommandContext->UploadTexture(
m_Texture.get(), m_TextureFormat, texData.get(), textureDataSize);
texData.reset();
{
// Texture matrix: We want to map
// world pos (0, y, 0) (i.e. first vertex)
// onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel);
// world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex)
// onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel)
float s = (m_MapSize-1) / static_cast(textureSize * (m_MapSize-1) * LOS_TILE_SIZE);
float t = 0.5f / textureSize;
m_TextureMatrix.SetZero();
m_TextureMatrix._11 = s;
m_TextureMatrix._23 = s;
m_TextureMatrix._14 = t;
m_TextureMatrix._24 = t;
m_TextureMatrix._44 = 1;
}
{
// Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize)
float s = m_MapSize / (float)textureSize;
m_MinimapTextureMatrix.SetZero();
m_MinimapTextureMatrix._11 = s;
m_MinimapTextureMatrix._22 = s;
m_MinimapTextureMatrix._44 = 1;
}
}
void CLOSTexture::RecomputeTexture(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
// If the map was resized, delete and regenerate the texture
if (m_Texture)
{
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide())
DeleteTexture();
}
bool recreated = false;
if (!m_Texture)
{
ConstructTexture(deviceCommandContext);
recreated = true;
}
PROFILE("recompute LOS texture");
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
size_t pitch;
const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch);
ENSURE(pitch * m_MapSize <= dataSize);
std::unique_ptr losData = std::make_unique(
dataSize * m_TextureFormatStride);
CLosQuerier los(cmpRangeManager->GetLosQuerier(m_Simulation.GetSimContext().GetCurrentDisplayedPlayer()));
GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch);
// GenerateBitmap writes data tightly packed and we need to offset it to fit
// into the texture format properly.
const size_t textureDataPitch = pitch * m_TextureFormatStride;
if (m_TextureFormatStride > 1)
{
// We skip the last byte because it will be first in our order and we
// don't need to move it.
for (size_t index = 0; index + 1 < dataSize; ++index)
{
const size_t oldAddress = dataSize - 1 - index;
const size_t newAddress = oldAddress * m_TextureFormatStride;
losData[newAddress] = losData[oldAddress];
losData[oldAddress] = 0;
}
}
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated)
{
deviceCommandContext->UploadTextureRegion(
m_SmoothTextures[0].get(), m_TextureFormat, losData.get(),
textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize);
deviceCommandContext->UploadTextureRegion(
m_SmoothTextures[1].get(), m_TextureFormat, losData.get(),
textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize);
}
deviceCommandContext->UploadTextureRegion(
m_Texture.get(), m_TextureFormat, losData.get(),
textureDataPitch * m_MapSize, 0, 0, pitch, m_MapSize);
}
size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch)
{
*pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment);
return *pitch * (h + g_BlurSize - 1);
}
void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch)
{
u8 *dataPtr = losData;
// Initialise the top padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
for (size_t j = 0; j < h; ++j)
{
// Initialise the left padding
for (size_t i = 0; i < g_BlurSize/2; ++i)
*dataPtr++ = 0;
// Fill in the visibility data
for (size_t i = 0; i < w; ++i)
{
if (los.IsVisible_UncheckedRange(i, j))
*dataPtr++ = 255;
else if (los.IsExplored_UncheckedRange(i, j))
*dataPtr++ = 127;
else
*dataPtr++ = 0;
}
// Initialise the right padding
for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i)
*dataPtr++ = 0;
}
// Initialise the bottom padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
// Horizontal blur:
for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0] +
6*d[1] +
15*d[2] +
20*d[3] +
15*d[4] +
6*d[5] +
1*d[6]
) / 64;
}
}
// Vertical blur:
for (size_t j = 0; j < h; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0*pitch] +
6*d[1*pitch] +
15*d[2*pitch] +
20*d[3*pitch] +
15*d[4*pitch] +
6*d[5*pitch] +
1*d[6*pitch]
) / 64;
}
}
}
Index: ps/trunk/source/graphics/MiniMapTexture.cpp
===================================================================
--- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 27312)
+++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 27313)
@@ -1,854 +1,855 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "MiniMapTexture.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniPatch.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgramPtr.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/TextureManager.h"
#include "lib/bits.h"
#include "lib/hash.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/backend/IDevice.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/Object.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/system/ParamNode.h"
#include
#include
#include
namespace
{
// Set max drawn entities to 64K / 4 for now, which is more than enough.
// 4 is the number of vertices per entity.
// TODO: we should be cleverer about drawing them to reduce clutter,
// f.e. use instancing.
constexpr size_t MAX_ENTITIES_DRAWN = 65536 / 4;
constexpr size_t MAX_ICON_COUNT = 256;
constexpr size_t MAX_UNIQUE_ICON_COUNT = 64;
constexpr size_t ICON_COMBINING_GRID_SIZE = 10;
constexpr size_t FINAL_TEXTURE_SIZE = 512;
unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color >> 8) & 0xff) * x);
unsigned int b = unsigned(float((color >> 16) & 0xff) * x);
return (0xff000000 | b | g << 8 | r << 16);
}
void DrawTexture(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
const float quadUVs[] =
{
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f
};
const float quadVertices[] =
{
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f,
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
};
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1);
deviceCommandContext->SetVertexBufferData(
0, quadVertices, std::size(quadVertices) * sizeof(quadVertices[0]));
deviceCommandContext->SetVertexBufferData(
1, quadUVs, std::size(quadUVs) * sizeof(quadUVs[0]));
deviceCommandContext->Draw(0, 6);
}
struct MinimapUnitVertex
{
// This struct is copyable for convenience and because to move is to copy for primitives.
u8 r, g, b, a;
CVector2D position;
};
// Adds a vertex to the passed VertexArray
inline void AddEntity(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos,
const float entityRadius,
const bool useInstancing)
{
if (useInstancing)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.position.X;
(*attrPos)[1] = v.position.Y;
++attrPos;
return;
}
const CVector2D offsets[4] =
{
{-entityRadius, 0.0f},
{0.0f, -entityRadius},
{entityRadius, 0.0f},
{0.0f, entityRadius}
};
for (const CVector2D& offset : offsets)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.position.X + offset.X;
(*attrPos)[1] = v.position.Y + offset.Y;
++attrPos;
}
}
} // anonymous namespace
size_t CMiniMapTexture::CellIconKeyHash::operator()(
const CellIconKey& key) const
{
size_t seed = 0;
hash_combine(seed, key.path);
hash_combine(seed, key.r);
hash_combine(seed, key.g);
hash_combine(seed, key.b);
return seed;
}
bool CMiniMapTexture::CellIconKeyEqual::operator()(
const CellIconKey& lhs, const CellIconKey& rhs) const
{
return
lhs.path == rhs.path &&
lhs.r == rhs.r &&
lhs.g == rhs.g &&
lhs.b == rhs.b;
}
CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
: m_Simulation(simulation), m_IndexArray(false),
m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true),
m_InstanceVertexArray(Renderer::Backend::IBuffer::Type::VERTEX, false)
{
// Register Relax NG validator.
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
m_ShallowPassageHeight = GetShallowPassageHeight();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
}
m_HalfBlinkDuration = blinkDuration / 2.0;
m_AttributePos.format = Renderer::Backend::Format::R32G32_SFLOAT;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4);
m_VertexArray.Layout();
m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i)
*index++ = 0;
m_IndexArray.Upload();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++i)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
if (g_VideoMode.GetBackendDevice()->GetCapabilities().instancing)
{
m_UseInstancing = true;
const size_t numberOfCircleSegments = 8;
m_InstanceAttributePosition.format = Renderer::Backend::Format::R32G32_SFLOAT;
m_InstanceVertexArray.AddAttribute(&m_InstanceAttributePosition);
m_InstanceVertexArray.SetNumberOfVertices(numberOfCircleSegments * 3);
m_InstanceVertexArray.Layout();
VertexArrayIterator attributePosition =
m_InstanceAttributePosition.GetIterator();
for (size_t segment = 0; segment < numberOfCircleSegments; ++segment)
{
const float currentAngle = static_cast(segment) / numberOfCircleSegments * 2.0f * M_PI;
const float nextAngle = static_cast(segment + 1) / numberOfCircleSegments * 2.0f * M_PI;
(*attributePosition)[0] = 0.0f;
(*attributePosition)[1] = 0.0f;
++attributePosition;
(*attributePosition)[0] = std::cos(currentAngle);
(*attributePosition)[1] = std::sin(currentAngle);
++attributePosition;
(*attributePosition)[0] = std::cos(nextAngle);
(*attributePosition)[1] = std::sin(nextAngle);
++attributePosition;
}
m_InstanceVertexArray.Upload();
m_InstanceVertexArray.FreeBackingStore();
}
}
CMiniMapTexture::~CMiniMapTexture()
{
DestroyTextures();
}
void CMiniMapTexture::Update(const float UNUSED(deltaRealTime))
{
if (m_WaterHeight != g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight)
{
m_TerrainTextureDirty = true;
m_FinalTextureDirty = true;
}
}
void CMiniMapTexture::Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CLOSTexture& losTexture, CTerritoryTexture& territoryTexture)
{
const CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain)
return;
if (!m_TerrainTexture)
CreateTextures(deviceCommandContext, terrain);
if (m_TerrainTextureDirty)
RebuildTerrainTexture(deviceCommandContext, terrain);
RenderFinalTexture(deviceCommandContext, losTexture, territoryTexture);
}
void CMiniMapTexture::CreateTextures(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const CTerrain* terrain)
{
DestroyTextures();
m_MapSize = terrain->GetVerticesPerSide();
const size_t textureSize = round_up_to_pow2(static_cast(m_MapSize));
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
Renderer::Backend::IDevice* backendDevice = deviceCommandContext->GetDevice();
// Create terrain texture
m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture",
Renderer::Backend::ITexture::Usage::TRANSFER_DST |
Renderer::Backend::ITexture::Usage::SAMPLED,
Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, defaultSamplerDesc);
// Initialise texture with solid black, for the areas we don't
// overwrite with uploading later.
std::unique_ptr texData = std::make_unique(textureSize * textureSize);
for (size_t i = 0; i < textureSize * textureSize; ++i)
texData[i] = 0xFF000000;
deviceCommandContext->UploadTexture(
m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
texData.get(), textureSize * textureSize * 4);
texData.reset();
m_TerrainData = std::make_unique((m_MapSize - 1) * (m_MapSize - 1));
m_FinalTexture = g_Renderer.GetTextureManager().WrapBackendTexture(
backendDevice->CreateTexture2D("MiniMapFinalTexture",
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
Renderer::Backend::Format::R8G8B8A8_UNORM,
FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc));
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = m_FinalTexture->GetBackendTexture();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer(
"MiniMapFinalFramebuffer", &colorAttachment, nullptr);
ENSURE(m_FinalTextureFramebuffer);
}
void CMiniMapTexture::DestroyTextures()
{
m_TerrainTexture.reset();
m_FinalTexture.reset();
m_TerrainData.reset();
}
void CMiniMapTexture::RebuildTerrainTexture(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CTerrain* terrain)
{
const u32 x = 0;
const u32 y = 0;
const u32 width = m_MapSize - 1;
const u32 height = m_MapSize - 1;
m_WaterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight;
m_TerrainTextureDirty = false;
for (u32 j = 0; j < height; ++j)
{
u32* dataPtr = m_TerrainData.get() + ((y + j) * width) + x;
for (u32 i = 0; i < width; ++i)
{
const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j)
+ terrain->GetVertexGroundLevel((int)i+1, (int)j)
+ terrain->GetVertexGroundLevel((int)i, (int)j+1)
+ terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
) / 4.0f;
if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
{
// shallow water
*dataPtr++ = 0xffc09870;
}
else if (avgHeight < m_WaterHeight)
{
// Set water as constant color for consistency on different maps
*dataPtr++ = 0xffa07850;
}
else
{
int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
int val = (hmap / 3) + 170;
u32 color = 0xFFFFFFFF;
CMiniPatch* mp = terrain->GetTile(x + i, y + j);
if (mp)
{
CTerrainTextureEntry* tex = mp->GetTextureEntry();
if (tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if (!tex->GetTexture()->TryLoad())
m_TerrainTextureDirty = true;
color = tex->GetBaseColor();
}
}
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
}
}
}
// Upload the texture
deviceCommandContext->UploadTextureRegion(
m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
m_TerrainData.get(), width * height * 4, 0, 0, width, height);
}
void CMiniMapTexture::RenderFinalTexture(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
CLOSTexture& losTexture, CTerritoryTexture& territoryTexture)
{
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
const double currentTime = timer_Time();
const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty;
if (!doUpdate)
return;
m_LastFinalTextureUpdate = currentTime;
m_FinalTextureDirty = false;
// We might scale entities properly in the vertex shader but it requires
// additional space in the vertex buffer. So we assume that we don't need
// to change an entity size so often.
// Radius with instancing is lower because an entity has a more round shape.
const float entityRadius = static_cast(m_MapSize) / 128.0f * (m_UseInstancing ? 5.0 : 6.0f);
UpdateAndUploadEntities(deviceCommandContext, entityRadius, currentTime);
PROFILE3("Render minimap texture");
GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture");
deviceCommandContext->BeginFramebufferPass(m_FinalTextureFramebuffer.get());
- const SViewPort oldViewPort = g_Renderer.GetViewport();
- const SViewPort viewPort = { 0, 0, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE };
- g_Renderer.SetViewport(viewPort);
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = FINAL_TEXTURE_SIZE;
+ viewportRect.height = FINAL_TEXTURE_SIZE;
+ deviceCommandContext->SetViewports(1, &viewportRect);
const float texCoordMax = m_TerrainTexture ? static_cast(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f;
Renderer::Backend::IShaderProgram* shader = nullptr;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines);
- Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
- tech->GetGraphicsPipelineStateDesc();
- deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
+ deviceCommandContext->SetGraphicsPipelineState(
+ tech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
shader = tech->GetShader();
if (m_TerrainTexture)
{
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_baseTex), m_TerrainTexture.get());
}
CMatrix3D baseTransform;
baseTransform.SetIdentity();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
CMatrix3D terrainTransform;
terrainTransform.SetIdentity();
terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_transform),
baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_textureTransform),
terrainTransform._11, terrainTransform._21, terrainTransform._12, terrainTransform._22);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_translation),
baseTransform._14, baseTransform._24, terrainTransform._14, terrainTransform._24);
if (m_TerrainTexture)
DrawTexture(deviceCommandContext);
deviceCommandContext->EndPass();
+ Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
+ tech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.blendState.enabled = true;
pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
Renderer::Backend::BlendFactor::SRC_ALPHA;
pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
Renderer::Backend::BlendOp::ADD;
pipelineStateDesc.blendState.colorWriteMask =
Renderer::Backend::ColorWriteMask::RED |
Renderer::Backend::ColorWriteMask::GREEN |
Renderer::Backend::ColorWriteMask::BLUE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
deviceCommandContext->BeginPass();
// Draw territory boundaries
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_baseTex), territoryTexture.GetTexture());
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_transform),
baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
const CMatrix3D& territoryTransform = territoryTexture.GetMinimapTextureMatrix();
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_textureTransform),
territoryTransform._11, territoryTransform._21, territoryTransform._12, territoryTransform._22);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_translation),
baseTransform._14, baseTransform._24, territoryTransform._14, territoryTransform._24);
DrawTexture(deviceCommandContext);
deviceCommandContext->EndPass();
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap_los, CShaderDefines());
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
shader = tech->GetShader();
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_baseTex), losTexture.GetTexture());
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_transform),
baseTransform._11, baseTransform._21, baseTransform._12, baseTransform._22);
const CMatrix3D& losTransform = losTexture.GetMinimapTextureMatrix();
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_textureTransform),
losTransform._11, losTransform._21, losTransform._12, losTransform._22);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_translation),
baseTransform._14, baseTransform._24, losTransform._14, losTransform._24);
DrawTexture(deviceCommandContext);
deviceCommandContext->EndPass();
if (m_EntitiesDrawn > 0)
DrawEntities(deviceCommandContext, entityRadius);
deviceCommandContext->EndFramebufferPass();
- g_Renderer.SetViewport(oldViewPort);
}
void CMiniMapTexture::UpdateAndUploadEntities(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const float entityRadius, const double& currentTime)
{
const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize);
m_Icons.clear();
m_IconsCache.clear();
CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap);
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
if (currentTime > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = currentTime + m_HalfBlinkDuration;
}
bool iconsEnabled = false;
CFG_GET_VAL("gui.session.minimap.icons.enabled", iconsEnabled);
float iconsOpacity = 1.0f;
CFG_GET_VAL("gui.session.minimap.icons.opacity", iconsOpacity);
float iconsSizeScale = 1.0f;
CFG_GET_VAL("gui.session.minimap.icons.sizescale", iconsSizeScale);
bool iconsCountOverflow = false;
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer());
if (vis != LosVisibility::HIDDEN)
{
v.a = 255;
v.position.X = posX.ToFloat();
v.position.Y = posZ.ToFloat();
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
AddEntity(v, attrColor, attrPos, entityRadius, m_UseInstancing);
++m_EntitiesDrawn;
}
if (!iconsEnabled || !cmpMinimap->HasIcon())
continue;
const CellIconKey key{
cmpMinimap->GetIconPath(), v.r, v.g, v.b};
const u16 gridX = Clamp(
(v.position.X * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1);
const u16 gridY = Clamp(
(v.position.Y * invTileMapSize) * ICON_COMBINING_GRID_SIZE, 0, ICON_COMBINING_GRID_SIZE - 1);
CellIcon icon{
gridX, gridY, cmpMinimap->GetIconSize() * iconsSizeScale * 0.5f, v.position};
if (m_IconsCache.find(key) == m_IconsCache.end() && m_IconsCache.size() >= MAX_UNIQUE_ICON_COUNT)
{
iconsCountOverflow = true;
}
else
{
m_IconsCache[key].emplace_back(std::move(icon));
}
}
}
}
// We need to combine too close icons with the same path, we use a grid for
// that. But to save some allocations and space we store only the current
// row.
struct Cell
{
u32 count;
float maxHalfSize;
CVector2D averagePosition;
};
std::array gridRow;
for (auto& [key, icons] : m_IconsCache)
{
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(
CTextureProperties(key.path));
const CColor color(key.r / 255.0f, key.g / 255.0f, key.b / 255.0f, iconsOpacity);
std::sort(icons.begin(), icons.end(),
[](const CellIcon& lhs, const CellIcon& rhs) -> bool
{
if (lhs.gridY != rhs.gridY)
return lhs.gridY < rhs.gridY;
return lhs.gridX < rhs.gridX;
});
for (auto beginIt = icons.begin(); beginIt != icons.end();)
{
auto endIt = std::next(beginIt);
while (endIt != icons.end() && beginIt->gridY == endIt->gridY)
++endIt;
gridRow.fill({0, 0.0f, {}});
for (; beginIt != endIt; ++beginIt)
{
Cell& cell = gridRow[beginIt->gridX];
const float previousPositionWeight = static_cast(cell.count) / (cell.count + 1);
cell.averagePosition = cell.averagePosition * previousPositionWeight + beginIt->worldPosition / static_cast(cell.count + 1);
cell.maxHalfSize = std::max(cell.maxHalfSize, beginIt->halfSize);
++cell.count;
}
for (const Cell& cell : gridRow)
{
if (cell.count == 0)
continue;
if (m_Icons.size() < MAX_ICON_COUNT)
{
m_Icons.emplace_back(Icon{
texture, color, cell.averagePosition, cell.maxHalfSize});
}
else
iconsCountOverflow = true;
}
}
}
if (iconsCountOverflow)
LOGWARNING("Too many minimap icons to draw.");
// Add the pinged vertices at the end, so they are drawn on top
for (const MinimapUnitVertex& vertex : pingingVertices)
{
AddEntity(vertex, attrColor, attrPos, entityRadius, m_UseInstancing);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
if (!m_UseInstancing)
{
VertexArrayIterator index = m_IndexArray.GetIterator();
for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex)
{
index[entityIndex * 6 + 0] = static_cast(entityIndex * 4 + 0);
index[entityIndex * 6 + 1] = static_cast(entityIndex * 4 + 1);
index[entityIndex * 6 + 2] = static_cast(entityIndex * 4 + 2);
index[entityIndex * 6 + 3] = static_cast(entityIndex * 4 + 0);
index[entityIndex * 6 + 4] = static_cast(entityIndex * 4 + 2);
index[entityIndex * 6 + 5] = static_cast(entityIndex * 4 + 3);
}
m_IndexArray.Upload();
}
m_VertexArray.Upload();
m_VertexArray.PrepareForRendering();
m_VertexArray.UploadIfNeeded(deviceCommandContext);
if (!m_UseInstancing)
m_IndexArray.UploadIfNeeded(deviceCommandContext);
}
void CMiniMapTexture::DrawEntities(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const float entityRadius)
{
const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize);
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
if (m_UseInstancing)
pointDefines.Add(str_USE_GPU_INSTANCING, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines);
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* shader = tech->GetShader();
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Convert world space coordinates into [0, 2].
const float unitScale = invTileMapSize;
unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f);
// Offset the coordinates to [-1, 1].
unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f));
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_transform),
unitMatrix._11, unitMatrix._21, unitMatrix._12, unitMatrix._22);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_translation),
unitMatrix._14, unitMatrix._24, 0.0f, 0.0f);
Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
scissorRect.x = scissorRect.y = 1;
scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2;
deviceCommandContext->SetScissors(1, &scissorRect);
const uint32_t stride = m_VertexArray.GetStride();
const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride;
if (m_UseInstancing)
{
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
m_InstanceAttributePosition.format, m_InstanceAttributePosition.offset,
m_InstanceVertexArray.GetStride(),
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV1,
m_AttributePos.format, m_AttributePos.offset, stride,
Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::COLOR,
m_AttributeColor.format, m_AttributeColor.offset, stride,
Renderer::Backend::VertexAttributeRate::PER_INSTANCE, 1);
deviceCommandContext->SetVertexBuffer(
0, m_InstanceVertexArray.GetBuffer(), m_InstanceVertexArray.GetOffset());
deviceCommandContext->SetVertexBuffer(
1, m_VertexArray.GetBuffer(), firstVertexOffset);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), entityRadius);
deviceCommandContext->DrawInstanced(0, m_InstanceVertexArray.GetNumberOfVertices(), 0, m_EntitiesDrawn);
}
else
{
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
m_AttributePos.format, m_AttributePos.offset, stride,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::COLOR,
m_AttributeColor.format, m_AttributeColor.offset, stride,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexBuffer(
0, m_VertexArray.GetBuffer(), firstVertexOffset);
deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer());
deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0);
}
g_Renderer.GetStats().m_DrawCalls++;
deviceCommandContext->SetScissors(0, nullptr);
deviceCommandContext->EndPass();
}
// static
float CMiniMapTexture::GetShallowPassageHeight()
{
float shallowPassageHeight = 0.0f;
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
return shallowPassageHeight;
}
Index: ps/trunk/source/ps/VideoMode.cpp
===================================================================
--- ps/trunk/source/ps/VideoMode.cpp (revision 27312)
+++ ps/trunk/source/ps/VideoMode.cpp (revision 27313)
@@ -1,801 +1,801 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "VideoMode.h"
#include "graphics/GameView.h"
#include "gui/GUIManager.h"
#include "lib/config2.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/tex/tex.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStr.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/Pyrogenesis.h"
#include "renderer/backend/dummy/DeviceForward.h"
#include "renderer/backend/gl/DeviceForward.h"
#include "renderer/backend/IDevice.h"
#include "renderer/Renderer.h"
namespace
{
int DEFAULT_WINDOW_W = 1024;
int DEFAULT_WINDOW_H = 768;
int DEFAULT_FULLSCREEN_W = 1024;
int DEFAULT_FULLSCREEN_H = 768;
const wchar_t DEFAULT_CURSOR_NAME[] = L"default-arrow";
} // anonymous namespace
#if OS_WIN
// We can't include wutil directly because GL headers conflict with Windows
// until we use a proper GL loader.
extern void wutil_SetAppWindow(SDL_Window* window);
// After a proper HiDPI integration we should switch to manifest until
// SDL has an implemented HiDPI on Windows.
extern void wutil_EnableHiDPIOnWindows();
#endif
CVideoMode g_VideoMode;
class CVideoMode::CCursor
{
public:
enum class CursorBackend
{
SDL,
SYSTEM
};
CCursor();
~CCursor();
void SetCursor(const CStrW& name);
void ResetCursor();
private:
CursorBackend m_CursorBackend = CursorBackend::SYSTEM;
SDL_Surface* m_CursorSurface = nullptr;
SDL_Cursor* m_Cursor = nullptr;
CStrW m_CursorName;
};
CVideoMode::CCursor::CCursor()
{
std::string cursorBackend;
CFG_GET_VAL("cursorbackend", cursorBackend);
if (cursorBackend == "sdl")
m_CursorBackend = CursorBackend::SDL;
else
m_CursorBackend = CursorBackend::SYSTEM;
ResetCursor();
}
CVideoMode::CCursor::~CCursor()
{
if (m_Cursor)
SDL_FreeCursor(m_Cursor);
if (m_CursorSurface)
SDL_FreeSurface(m_CursorSurface);
}
void CVideoMode::CCursor::SetCursor(const CStrW& name)
{
if (m_CursorBackend == CursorBackend::SYSTEM || m_CursorName == name)
return;
m_CursorName = name;
if (m_Cursor)
SDL_FreeCursor(m_Cursor);
if (m_CursorSurface)
SDL_FreeSurface(m_CursorSurface);
if (name.empty())
{
SDL_ShowCursor(SDL_DISABLE);
return;
}
const VfsPath pathBaseName(VfsPath(L"art/textures/cursors") / name);
// Read pixel offset of the cursor's hotspot [the bit of it that's
// drawn at (g_mouse_x,g_mouse_y)] from file.
int hotspotX = 0, hotspotY = 0;
{
const VfsPath pathHotspotName = pathBaseName.ChangeExtension(L".txt");
std::shared_ptr buffer;
size_t size;
if (g_VFS->LoadFile(pathHotspotName, buffer, size) != INFO::OK)
{
LOGERROR("Can't load hotspot for cursor: %s", pathHotspotName.string8().c_str());
return;
}
std::wstringstream s(std::wstring(reinterpret_cast(buffer.get()), size));
s >> hotspotX >> hotspotY;
}
const VfsPath pathImageName = pathBaseName.ChangeExtension(L".png");
std::shared_ptr file;
size_t fileSize;
if (g_VFS->LoadFile(pathImageName, file, fileSize) != INFO::OK)
{
LOGERROR("Can't load image for cursor: %s", pathImageName.string8().c_str());
return;
}
Tex t;
if (t.decode(file, fileSize) != INFO::OK)
{
LOGERROR("Can't decode image for cursor");
return;
}
// Convert to required BGRA format.
const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
if (t.transform_to(flags) != INFO::OK)
{
LOGERROR("Can't transform image for cursor");
return;
}
void* imageBGRA = t.get_data();
if (!imageBGRA)
{
LOGERROR("Transformed image is empty for cursor");
return;
}
m_CursorSurface = SDL_CreateRGBSurfaceFrom(imageBGRA,
static_cast(t.m_Width), static_cast(t.m_Height), 32,
static_cast(t.m_Width * 4),
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (!m_CursorSurface)
{
LOGERROR("Can't create surface for cursor: %s", SDL_GetError());
return;
}
const float scale = g_VideoMode.GetScale();
if (scale != 1.0)
{
SDL_Surface* scaledSurface = SDL_CreateRGBSurface(0,
m_CursorSurface->w * scale,
m_CursorSurface->h * scale, 32,
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (!scaledSurface)
{
LOGERROR("Can't create scaled surface forcursor: %s", SDL_GetError());
return;
}
if (SDL_BlitScaled(m_CursorSurface, nullptr, scaledSurface, nullptr))
return;
SDL_FreeSurface(m_CursorSurface);
m_CursorSurface = scaledSurface;
}
m_Cursor = SDL_CreateColorCursor(m_CursorSurface, hotspotX, hotspotY);
if (!m_Cursor)
{
LOGERROR("Can't create cursor: %s", SDL_GetError());
return;
}
SDL_SetCursor(m_Cursor);
}
void CVideoMode::CCursor::ResetCursor()
{
SetCursor(DEFAULT_CURSOR_NAME);
}
CVideoMode::CVideoMode() :
m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
{
}
CVideoMode::~CVideoMode() = default;
void CVideoMode::ReadConfig()
{
bool windowed = !m_ConfigFullscreen;
CFG_GET_VAL("windowed", windowed);
m_ConfigFullscreen = !windowed;
CFG_GET_VAL("gui.scale", m_Scale);
CFG_GET_VAL("xres", m_ConfigW);
CFG_GET_VAL("yres", m_ConfigH);
CFG_GET_VAL("bpp", m_ConfigBPP);
CFG_GET_VAL("display", m_ConfigDisplay);
CFG_GET_VAL("hidpi", m_ConfigEnableHiDPI);
CFG_GET_VAL("vsync", m_ConfigVSync);
CStr rendererBackend;
CFG_GET_VAL("rendererbackend", rendererBackend);
if (rendererBackend == "glarb")
m_Backend = Renderer::Backend::Backend::GL_ARB;
else if (rendererBackend == "dummy")
m_Backend = Renderer::Backend::Backend::DUMMY;
else
m_Backend = Renderer::Backend::Backend::GL;
}
bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
{
Uint32 flags = 0;
if (fullscreen)
{
bool borderlessFullscreen = true;
CFG_GET_VAL("borderless.fullscreen", borderlessFullscreen);
flags |= borderlessFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
}
else
{
bool borderlessWindow = false;
CFG_GET_VAL("borderless.window", borderlessWindow);
if (borderlessWindow)
flags |= SDL_WINDOW_BORDERLESS;
}
if (!m_Window)
{
#if OS_WIN
if (m_ConfigEnableHiDPI)
wutil_EnableHiDPIOnWindows();
#endif
// Note: these flags only take affect in SDL_CreateWindow
flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
if (m_ConfigEnableHiDPI)
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
m_Window = SDL_CreateWindow(main_window_name, m_WindowedX, m_WindowedY, w, h, flags);
if (!m_Window)
{
// If fullscreen fails, try windowed mode
if (fullscreen)
{
LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
"%dx%d:%d (\"%hs\"), falling back to windowed mode",
w, h, bpp, SDL_GetError());
// Using default size for the window for now, as the attempted setting
// could be as large, or larger than the screen size.
return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
}
else
{
LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
return false;
}
}
if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
{
LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
return false;
}
#if OS_WIN
// We need to set the window for an error dialog.
wutil_SetAppWindow(m_Window);
#endif
if (!CreateBackendDevice(true))
{
LOGERROR("SetVideoMode failed in backend device creation: %dx%d:%d %d",
w, h, bpp, fullscreen ? 1 : 0);
return false;
}
}
else
{
if (m_IsFullscreen != fullscreen)
{
if (!fullscreen)
{
// For some reason, when switching from fullscreen to windowed mode,
// we have to set the window size and position before and after switching
SDL_SetWindowSize(m_Window, w, h);
SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
}
if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
{
LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
return false;
}
}
if (!fullscreen)
{
SDL_SetWindowSize(m_Window, w, h);
SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
}
}
// Grab the current video settings
SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
m_CurrentBPP = bpp;
if (fullscreen)
SDL_SetWindowGrab(m_Window, SDL_TRUE);
else
SDL_SetWindowGrab(m_Window, SDL_FALSE);
m_IsFullscreen = fullscreen;
g_xres = m_CurrentW;
g_yres = m_CurrentH;
return true;
}
bool CVideoMode::InitSDL()
{
ENSURE(!m_IsInitialised);
ReadConfig();
// preferred video mode = current desktop settings
// (command line params may override these)
// TODO: handle multi-screen and HiDPI properly.
SDL_DisplayMode mode;
if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
{
m_PreferredW = mode.w;
m_PreferredH = mode.h;
m_PreferredBPP = SDL_BITSPERPIXEL(mode.format);
m_PreferredFreq = mode.refresh_rate;
}
int w = m_ConfigW;
int h = m_ConfigH;
if (m_ConfigFullscreen)
{
// If fullscreen and no explicit size set, default to the desktop resolution
if (w == 0 || h == 0)
{
w = m_PreferredW;
h = m_PreferredH;
}
}
// If no size determined, default to something sensible
if (w == 0 || h == 0)
{
w = DEFAULT_WINDOW_W;
h = DEFAULT_WINDOW_H;
}
if (!m_ConfigFullscreen)
{
// Limit the window to the screen size (if known)
if (m_PreferredW)
w = std::min(w, m_PreferredW);
if (m_PreferredH)
h = std::min(h, m_PreferredH);
}
int bpp = GetBestBPP();
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
bool debugContext = false;
CFG_GET_VAL("renderer.backend.debugcontext", debugContext);
if (debugContext)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
bool forceGLVersion = false;
CFG_GET_VAL("forceglversion", forceGLVersion);
if (forceGLVersion)
{
CStr forceGLProfile = "compatibility";
int forceGLMajorVersion = 3;
int forceGLMinorVersion = 0;
CFG_GET_VAL("forceglprofile", forceGLProfile);
CFG_GET_VAL("forceglmajorversion", forceGLMajorVersion);
CFG_GET_VAL("forceglminorversion", forceGLMinorVersion);
int profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
if (forceGLProfile == "es")
profile = SDL_GL_CONTEXT_PROFILE_ES;
else if (forceGLProfile == "core")
profile = SDL_GL_CONTEXT_PROFILE_CORE;
else if (forceGLProfile != "compatibility")
LOGWARNING("Unknown force GL profile '%s', compatibility profile is used", forceGLProfile.c_str());
if (forceGLMajorVersion < 1 || forceGLMinorVersion < 0)
{
LOGERROR("Unsupported force GL version: %d.%d", forceGLMajorVersion, forceGLMinorVersion);
}
else
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, forceGLMajorVersion);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, forceGLMinorVersion);
}
}
else
{
#if CONFIG2_GLES
// Require GLES 2.0
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
// Some macOS and MESA drivers might not create a context even if they can
// with the core profile. So disable it for a while until we can guarantee
// its creation.
#if OS_WIN
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#endif
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
#endif
}
if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
{
// Fall back to a smaller depth buffer
// (The rendering may be ugly but this helps when running in VMware)
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
return false;
}
SDL_GL_SetSwapInterval(m_ConfigVSync ? 1 : 0);
// Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
// The driver appears to register its own atexit hook on context creation.
// If this atexit hook is called before SDL_Quit destroys the OpenGL context,
// some kind of double-free problem causes a crash and lockup in the driver.
// Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
// by destroying the context *before* the driver's atexit hook is called.
// (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
atexit(SDL_Quit);
// End work around.
m_IsInitialised = true;
if (!m_ConfigFullscreen)
{
m_WindowedW = w;
m_WindowedH = h;
}
SetWindowIcon();
m_Cursor = std::make_unique();
return true;
}
bool CVideoMode::InitNonSDL()
{
ENSURE(!m_IsInitialised);
ReadConfig();
m_IsInitialised = true;
return true;
}
void CVideoMode::Shutdown()
{
ENSURE(m_IsInitialised);
m_Cursor.reset();
m_IsFullscreen = false;
m_IsInitialised = false;
m_BackendDevice.reset();
if (m_Window)
{
SDL_DestroyWindow(m_Window);
m_Window = nullptr;
}
}
bool CVideoMode::CreateBackendDevice(const bool createSDLContext)
{
if (m_Backend == Renderer::Backend::Backend::DUMMY)
{
m_BackendDevice = Renderer::Backend::Dummy::CreateDevice(m_Window);
ENSURE(m_BackendDevice);
return true;
}
m_BackendDevice = Renderer::Backend::GL::CreateDevice(createSDLContext ? m_Window : nullptr, m_Backend == Renderer::Backend::Backend::GL_ARB);
if (!m_BackendDevice && m_Backend == Renderer::Backend::Backend::GL)
{
LOGERROR("Unable to create device for GL backend, switching to ARB.");
m_Backend = Renderer::Backend::Backend::GL_ARB;
return CreateBackendDevice(createSDLContext);
}
return !!m_BackendDevice;
}
bool CVideoMode::ResizeWindow(int w, int h)
{
ENSURE(m_IsInitialised);
// Ignore if not windowed
if (m_IsFullscreen)
return true;
// Ignore if the size hasn't changed
if (w == m_WindowedW && h == m_WindowedH)
return true;
int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, false))
return false;
m_WindowedW = w;
m_WindowedH = h;
UpdateRenderer(w, h);
return true;
}
void CVideoMode::Rescale(float scale)
{
ENSURE(m_IsInitialised);
m_Scale = scale;
UpdateRenderer(m_CurrentW, m_CurrentH);
}
float CVideoMode::GetScale() const
{
return m_Scale;
}
bool CVideoMode::SetFullscreen(bool fullscreen)
{
// This might get called before initialisation by psDisplayError;
// if so then silently fail
if (!m_IsInitialised)
return false;
// Check whether this is actually a change
if (fullscreen == m_IsFullscreen)
return true;
if (!m_IsFullscreen)
{
// Windowed -> fullscreen:
int w = 0, h = 0;
// If a fullscreen size was configured, use that; else use the desktop size; else use a default
if (m_ConfigFullscreen)
{
w = m_ConfigW;
h = m_ConfigH;
}
if (w == 0 || h == 0)
{
w = m_PreferredW;
h = m_PreferredH;
}
if (w == 0 || h == 0)
{
w = DEFAULT_FULLSCREEN_W;
h = DEFAULT_FULLSCREEN_H;
}
int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, fullscreen))
return false;
UpdateRenderer(m_CurrentW, m_CurrentH);
return true;
}
else
{
// Fullscreen -> windowed:
// Go back to whatever the previous window size was
int w = m_WindowedW, h = m_WindowedH;
int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, fullscreen))
return false;
UpdateRenderer(w, h);
return true;
}
}
bool CVideoMode::ToggleFullscreen()
{
return SetFullscreen(!m_IsFullscreen);
}
bool CVideoMode::IsInFullscreen() const
{
return m_IsFullscreen;
}
void CVideoMode::UpdatePosition(int x, int y)
{
if (!m_IsFullscreen)
{
m_WindowedX = x;
m_WindowedY = y;
}
}
void CVideoMode::UpdateRenderer(int w, int h)
{
if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
if (h < 2) h = 2;
g_xres = w;
g_yres = h;
SViewPort vp = { 0, 0, w, h };
+ if (g_VideoMode.GetBackendDevice())
+ g_VideoMode.GetBackendDevice()->OnWindowResize(w, h);
+
if (CRenderer::IsInitialised())
- {
- g_Renderer.SetViewport(vp);
g_Renderer.Resize(w, h);
- }
if (g_GUI)
g_GUI->UpdateResolution();
if (g_Console)
g_Console->UpdateScreenSize(w, h);
if (g_Game)
g_Game->GetView()->SetViewport(vp);
}
int CVideoMode::GetBestBPP()
{
if (m_ConfigBPP)
return m_ConfigBPP;
if (m_PreferredBPP)
return m_PreferredBPP;
return 32;
}
int CVideoMode::GetXRes() const
{
ENSURE(m_IsInitialised);
return m_CurrentW;
}
int CVideoMode::GetYRes() const
{
ENSURE(m_IsInitialised);
return m_CurrentH;
}
int CVideoMode::GetBPP() const
{
ENSURE(m_IsInitialised);
return m_CurrentBPP;
}
bool CVideoMode::IsVSyncEnabled() const
{
ENSURE(m_IsInitialised);
return m_ConfigVSync;
}
int CVideoMode::GetDesktopXRes() const
{
ENSURE(m_IsInitialised);
return m_PreferredW;
}
int CVideoMode::GetDesktopYRes() const
{
ENSURE(m_IsInitialised);
return m_PreferredH;
}
int CVideoMode::GetDesktopBPP() const
{
ENSURE(m_IsInitialised);
return m_PreferredBPP;
}
int CVideoMode::GetDesktopFreq() const
{
ENSURE(m_IsInitialised);
return m_PreferredFreq;
}
SDL_Window* CVideoMode::GetWindow()
{
ENSURE(m_IsInitialised);
return m_Window;
}
void CVideoMode::SetWindowIcon()
{
// The window icon should be kept outside of art/textures/, or else it will be converted
// to DDS by the archive builder and will become unusable here. Using DDS makes BGRA
// conversion needlessly complicated.
std::shared_ptr iconFile;
size_t iconFileSize;
if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK)
{
LOGWARNING("Window icon not found.");
return;
}
Tex iconTexture;
if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK)
return;
// Convert to required BGRA format.
const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT;
if (iconTexture.transform_to(iconFlags) != INFO::OK)
return;
void* bgra_img = iconTexture.get_data();
if (!bgra_img)
return;
SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img,
iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4,
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (!iconSurface)
return;
SDL_SetWindowIcon(m_Window, iconSurface);
SDL_FreeSurface(iconSurface);
}
void CVideoMode::SetCursor(const CStrW& name)
{
if (m_Cursor)
m_Cursor->SetCursor(name);
}
void CVideoMode::ResetCursor()
{
if (m_Cursor)
m_Cursor->ResetCursor();
}
Index: ps/trunk/source/renderer/PostprocManager.cpp
===================================================================
--- ps/trunk/source/renderer/PostprocManager.cpp (revision 27312)
+++ ps/trunk/source/renderer/PostprocManager.cpp (revision 27313)
@@ -1,747 +1,745 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "renderer/PostprocManager.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
#include "lib/bits.h"
#include "maths/MathUtil.h"
#include "ps/ConfigDB.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/backend/IDevice.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include
CPostprocManager::CPostprocManager()
: m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true),
m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0)
{
}
CPostprocManager::~CPostprocManager()
{
Cleanup();
}
bool CPostprocManager::IsEnabled() const
{
Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice();
return
g_RenderingOptions.GetPostProc() &&
device->GetBackend() != Renderer::Backend::Backend::GL_ARB &&
device->IsTextureFormatSupported(Renderer::Backend::Format::D24_S8);
}
void CPostprocManager::Cleanup()
{
if (!m_IsInitialized) // Only cleanup if previously used
return;
m_CaptureFramebuffer.reset();
m_PingFramebuffer.reset();
m_PongFramebuffer.reset();
m_ColorTex1.reset();
m_ColorTex2.reset();
m_DepthTex.reset();
for (BlurScale& scale : m_BlurScales)
{
for (BlurScale::Step& step : scale.steps)
{
step.framebuffer.reset();
step.texture.reset();
}
}
}
void CPostprocManager::Initialize()
{
if (m_IsInitialized)
return;
const uint32_t maxSamples = g_VideoMode.GetBackendDevice()->GetCapabilities().maxSampleCount;
const uint32_t possibleSampleCounts[] = {2, 4, 8, 16};
std::copy_if(
std::begin(possibleSampleCounts), std::end(possibleSampleCounts),
std::back_inserter(m_AllowedSampleCounts),
[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();
RecreateBuffers();
m_IsInitialized = true;
// Once we have initialised the buffers, we can update the techniques.
UpdateAntiAliasingTechnique();
UpdateSharpeningTechnique();
UpdateSharpnessFactor();
// This might happen after the map is loaded and the effect chosen
SetPostEffect(m_PostProcEffect);
}
void CPostprocManager::Resize()
{
m_Width = g_Renderer.GetWidth();
m_Height = g_Renderer.GetHeight();
// If the buffers were intialized, recreate them to the new size.
if (m_IsInitialized)
RecreateBuffers();
}
void CPostprocManager::RecreateBuffers()
{
Cleanup();
Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
#define GEN_BUFFER_RGBA(name, w, h) \
name = backendDevice->CreateTexture2D( \
"PostProc" #name, \
Renderer::Backend::ITexture::Usage::SAMPLED | \
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT, \
Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \
Renderer::Backend::Sampler::MakeDefaultSampler( \
Renderer::Backend::Sampler::Filter::LINEAR, \
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
// Two fullscreen ping-pong textures.
GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height);
GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height);
// 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
// to the blur helper functions.
uint32_t width = m_Width / 2, height = m_Height / 2;
for (BlurScale& scale : m_BlurScales)
{
for (BlurScale::Step& step : scale.steps)
{
GEN_BUFFER_RGBA(step.texture, width, height);
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = step.texture.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
step.framebuffer = backendDevice->CreateFramebuffer(
"BlurScaleSteoFramebuffer", &colorAttachment, nullptr);
}
width /= 2;
height /= 2;
}
#undef GEN_BUFFER_RGBA
// Allocate the Depth/Stencil texture.
m_DepthTex = backendDevice->CreateTexture2D("PostProcDepthTexture",
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
Renderer::Backend::Format::D24_S8, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
// Set up the framebuffers with some initial textures.
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = m_ColorTex1.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
depthStencilAttachment.texture = m_DepthTex.get();
depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
m_CaptureFramebuffer = backendDevice->CreateFramebuffer("PostprocCaptureFramebuffer",
&colorAttachment, &depthStencilAttachment);
colorAttachment.texture = m_ColorTex1.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
m_PingFramebuffer = backendDevice->CreateFramebuffer("PostprocPingFramebuffer",
&colorAttachment, nullptr);
colorAttachment.texture = m_ColorTex2.get();
m_PongFramebuffer = backendDevice->CreateFramebuffer("PostprocPongFramebuffer",
&colorAttachment, nullptr);
if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer)
{
LOGWARNING("Failed to create postproc framebuffers");
g_RenderingOptions.SetPostProc(false);
}
if (m_UsingMultisampleBuffer)
{
DestroyMultisampleBuffer();
CreateMultisampleBuffer();
}
}
void CPostprocManager::ApplyBlurDownscale2x(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::IFramebuffer* framebuffer,
Renderer::Backend::ITexture* inTex, int inWidth, int inHeight)
{
deviceCommandContext->BeginFramebufferPass(framebuffer);
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = inWidth / 2;
+ viewportRect.height = inHeight / 2;
+ deviceCommandContext->SetViewports(1, &viewportRect);
+
// Get bloom shader with instructions to simply copy texels.
CShaderDefines defines;
defines.Add(str_BLOOM_NOP, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines);
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* shader = tech->GetShader();
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_renderedTex), inTex);
- const SViewPort oldVp = g_Renderer.GetViewport();
- const SViewPort vp = { 0, 0, inWidth / 2, inHeight / 2 };
- g_Renderer.SetViewport(vp);
-
// TODO: remove the fullscreen quad drawing duplication.
float quadVerts[] =
{
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] =
{
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1);
deviceCommandContext->SetVertexBufferData(
0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
deviceCommandContext->SetVertexBufferData(
1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
deviceCommandContext->Draw(0, 6);
- g_Renderer.SetViewport(oldVp);
-
deviceCommandContext->EndPass();
deviceCommandContext->EndFramebufferPass();
}
void CPostprocManager::ApplyBlurGauss(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::ITexture* inTex,
Renderer::Backend::ITexture* tempTex,
Renderer::Backend::IFramebuffer* tempFramebuffer,
Renderer::Backend::IFramebuffer* outFramebuffer,
int inWidth, int inHeight)
{
deviceCommandContext->BeginFramebufferPass(tempFramebuffer);
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = inWidth;
+ viewportRect.height = inHeight;
+ deviceCommandContext->SetViewports(1, &viewportRect);
+
// Get bloom shader, for a horizontal Gaussian blur pass.
CShaderDefines defines2;
defines2.Add(str_BLOOM_PASS_H, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines2);
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* shader = tech->GetShader();
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_renderedTex), inTex);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_texSize), inWidth, inHeight);
- const SViewPort oldVp = g_Renderer.GetViewport();
- const SViewPort vp = { 0, 0, inWidth, inHeight };
- g_Renderer.SetViewport(vp);
-
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->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1);
deviceCommandContext->SetVertexBufferData(
0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
deviceCommandContext->SetVertexBufferData(
1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
deviceCommandContext->Draw(0, 6);
- g_Renderer.SetViewport(oldVp);
-
deviceCommandContext->EndPass();
deviceCommandContext->EndFramebufferPass();
deviceCommandContext->BeginFramebufferPass(outFramebuffer);
+ deviceCommandContext->SetViewports(1, &viewportRect);
+
// Get bloom shader, for a vertical Gaussian blur pass.
CShaderDefines defines3;
defines3.Add(str_BLOOM_PASS_V, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines3);
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
shader = tech->GetShader();
// Our input texture to the shader is the output of the horizontal pass.
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_renderedTex), tempTex);
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_texSize), inWidth, inHeight);
- g_Renderer.SetViewport(vp);
-
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1);
deviceCommandContext->SetVertexBufferData(
0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
deviceCommandContext->SetVertexBufferData(
1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
deviceCommandContext->Draw(0, 6);
- g_Renderer.SetViewport(oldVp);
-
deviceCommandContext->EndPass();
deviceCommandContext->EndFramebufferPass();
}
void CPostprocManager::ApplyBlur(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
uint32_t width = m_Width, height = m_Height;
Renderer::Backend::ITexture* previousTexture =
(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
for (BlurScale& scale : m_BlurScales)
{
ApplyBlurDownscale2x(deviceCommandContext, scale.steps[0].framebuffer.get(), previousTexture, width, height);
width /= 2;
height /= 2;
ApplyBlurGauss(deviceCommandContext, scale.steps[0].texture.get(),
scale.steps[1].texture.get(), scale.steps[1].framebuffer.get(),
scale.steps[0].framebuffer.get(), width, height);
}
}
-
-void CPostprocManager::CaptureRenderOutput(
- Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
+Renderer::Backend::IFramebuffer* CPostprocManager::PrepareAndGetOutputFramebuffer()
{
ENSURE(m_IsInitialized);
// Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point.
- deviceCommandContext->BeginFramebufferPass(
- m_UsingMultisampleBuffer ? m_MultisampleFramebuffer.get() : m_CaptureFramebuffer.get());
-
m_WhichBuffer = true;
-}
+ return m_UsingMultisampleBuffer ? m_MultisampleFramebuffer.get() : m_CaptureFramebuffer.get();
+}
-void CPostprocManager::ReleaseRenderOutput(
+void CPostprocManager::BlitOutputFramebuffer(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::IFramebuffer* destination)
{
ENSURE(m_IsInitialized);
GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer");
// We blit to the backbuffer from the previous active buffer.
deviceCommandContext->BlitFramebuffer(
destination, (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get());
}
void CPostprocManager::ApplyEffect(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderTechniquePtr& shaderTech, int pass)
{
// select the other FBO for rendering
- deviceCommandContext->BeginFramebufferPass(
- (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get());
+ Renderer::Backend::IFramebuffer* framebuffer =
+ (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get();
+ deviceCommandContext->BeginFramebufferPass(framebuffer);
+
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = framebuffer->GetWidth();
+ viewportRect.height = framebuffer->GetHeight();
+ deviceCommandContext->SetViewports(1, &viewportRect);
deviceCommandContext->SetGraphicsPipelineState(
shaderTech->GetGraphicsPipelineStateDesc(pass));
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(pass);
// Use the textures from the current FBO 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(
shader->GetBindingSlot(str_renderedTex),
m_WhichBuffer ? m_ColorTex1.get() : m_ColorTex2.get());
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_depthTex), m_DepthTex.get());
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_blurTex2), m_BlurScales[0].steps[0].texture.get());
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_blurTex4), m_BlurScales[1].steps[0].texture.get());
deviceCommandContext->SetTexture(
shader->GetBindingSlot(str_blurTex8), m_BlurScales[2].steps[0].texture.get());
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), m_Width);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_height), m_Height);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zNear), m_NearPlane);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zFar), m_FarPlane);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_sharpness), m_Sharpness);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_brightness), g_LightEnv.m_Brightness);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_hdr), g_LightEnv.m_Contrast);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation);
deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom);
float quadVerts[] =
{
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] =
{
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1);
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->EndFramebufferPass();
m_WhichBuffer = !m_WhichBuffer;
}
void CPostprocManager::ApplyPostproc(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
ENSURE(m_IsInitialized);
// Don't do anything if we are using the default effect and no AA.
const bool hasEffects = m_PostProcEffect != L"default";
const bool hasARB = g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB;
const bool hasAA = m_AATech && !hasARB;
const bool hasSharp = m_SharpTech && !hasARB;
if (!hasEffects && !hasAA && !hasSharp)
return;
GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc");
if (hasEffects)
{
// First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied!
// (This may need to change depending on future usage, however that will have a fps hit)
ApplyBlur(deviceCommandContext);
for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_PostProcTech, pass);
}
if (hasAA)
{
for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_AATech, pass);
}
if (hasSharp)
{
for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_SharpTech, pass);
}
}
// Generate list of available effect-sets
std::vector CPostprocManager::GetPostEffects()
{
std::vector effects;
const VfsPath folder(L"shaders/effects/postproc/");
VfsPaths pathnames;
if (vfs::GetPathnames(g_VFS, folder, 0, pathnames) < 0)
LOGERROR("Error finding Post effects in '%s'", folder.string8());
for (const VfsPath& path : pathnames)
if (path.Extension() == L".xml")
effects.push_back(path.Basename().string());
// Add the default "null" effect to the list.
effects.push_back(L"default");
sort(effects.begin(), effects.end());
return effects;
}
void CPostprocManager::SetPostEffect(const CStrW& name)
{
if (m_IsInitialized)
{
if (name != L"default")
{
CStrW n = L"postproc/" + name;
m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8()));
}
}
m_PostProcEffect = name;
}
void CPostprocManager::UpdateAntiAliasingTechnique()
{
Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice();
if (device->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
return;
CStr newAAName;
CFG_GET_VAL("antialiasing", newAAName);
if (m_AAName == newAAName)
return;
m_AAName = newAAName;
m_AATech.reset();
if (m_UsingMultisampleBuffer)
{
m_UsingMultisampleBuffer = false;
DestroyMultisampleBuffer();
}
// We have to hardcode names in the engine, because anti-aliasing
// techinques strongly depend on the graphics pipeline.
// We might use enums in future though.
constexpr std::string_view msaaPrefix{"msaa"};
if (m_AAName == "fxaa")
{
m_AATech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("fxaa"));
}
else if (m_AAName.size() > msaaPrefix.size() &&
std::string_view{m_AAName}.substr(0, msaaPrefix.size()) == msaaPrefix)
{
// We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas.
if (g_AtlasGameLoop && g_AtlasGameLoop->running)
return;
if (!device->GetCapabilities().multisampling || m_AllowedSampleCounts.empty())
{
LOGWARNING("MSAA is unsupported.");
return;
}
std::stringstream ss(m_AAName.substr(msaaPrefix.size()));
ss >> m_MultisampleCount;
if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) ==
std::end(m_AllowedSampleCounts))
{
m_MultisampleCount = std::min(4u, device->GetCapabilities().maxSampleCount);
LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str());
}
m_UsingMultisampleBuffer = true;
CreateMultisampleBuffer();
}
}
void CPostprocManager::UpdateSharpeningTechnique()
{
if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
return;
CStr newSharpName;
CFG_GET_VAL("sharpening", newSharpName);
if (m_SharpName == newSharpName)
return;
m_SharpName = newSharpName;
m_SharpTech.reset();
if (m_SharpName == "cas")
{
m_SharpTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(m_SharpName));
}
}
void CPostprocManager::UpdateSharpnessFactor()
{
CFG_GET_VAL("sharpness", m_Sharpness);
}
void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane)
{
m_NearPlane = nearPlane;
m_FarPlane = farPlane;
}
void CPostprocManager::CreateMultisampleBuffer()
{
Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS",
Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
// Allocate the Depth/Stencil texture.
m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS",
Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
Renderer::Backend::Format::D24_S8, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
// Set up the framebuffers with some initial textures.
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = m_MultisampleColorTex.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
depthStencilAttachment.texture = m_MultisampleDepthTex.get();
depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
m_MultisampleFramebuffer = backendDevice->CreateFramebuffer(
"PostprocMultisampleFramebuffer", &colorAttachment, &depthStencilAttachment);
if (!m_MultisampleFramebuffer)
{
LOGERROR("Failed to create postproc multisample framebuffer");
m_UsingMultisampleBuffer = false;
DestroyMultisampleBuffer();
}
}
void CPostprocManager::DestroyMultisampleBuffer()
{
if (m_UsingMultisampleBuffer)
return;
m_MultisampleFramebuffer.reset();
m_MultisampleColorTex.reset();
m_MultisampleDepthTex.reset();
}
bool CPostprocManager::IsMultisampleEnabled() const
{
return m_UsingMultisampleBuffer;
}
void CPostprocManager::ResolveMultisampleFramebuffer(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
if (!m_UsingMultisampleBuffer)
return;
GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample");
deviceCommandContext->BlitFramebuffer(
m_PingFramebuffer.get(), m_MultisampleFramebuffer.get());
}
Index: ps/trunk/source/renderer/PostprocManager.h
===================================================================
--- ps/trunk/source/renderer/PostprocManager.h (revision 27312)
+++ ps/trunk/source/renderer/PostprocManager.h (revision 27313)
@@ -1,187 +1,184 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_POSTPROCMANAGER
#define INCLUDED_POSTPROCMANAGER
#include "graphics/ShaderTechniquePtr.h"
#include "ps/CStr.h"
#include "renderer/backend/IFramebuffer.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/backend/ITexture.h"
#include
#include
class CPostprocManager
{
public:
CPostprocManager();
~CPostprocManager();
// Returns true if the the manager can be used.
bool IsEnabled() const;
// Create all buffers/textures in GPU memory and set default effect.
// @note Must be called before using in the renderer. May be called multiple times.
void Initialize();
// Update the size of the screen
void Resize();
// Returns a list of xml files found in shaders/effects/postproc.
static std::vector GetPostEffects();
// Returns the name of the current effect.
const CStrW& GetPostEffect() const
{
return m_PostProcEffect;
}
// Sets the current effect.
void SetPostEffect(const CStrW& name);
// Triggers update of shaders and FBO if needed.
void UpdateAntiAliasingTechnique();
void UpdateSharpeningTechnique();
void UpdateSharpnessFactor();
void SetDepthBufferClipPlanes(float nearPlane, float farPlane);
- // Clears the two color buffers and depth buffer, and redirects all rendering
- // to our textures instead of directly to the system framebuffer.
// @note CPostprocManager must be initialized first
- void CaptureRenderOutput(
- Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
+ Renderer::Backend::IFramebuffer* PrepareAndGetOutputFramebuffer();
// First renders blur textures, then calls ApplyEffect for each effect pass,
// ping-ponging the buffers at each step.
// @note CPostprocManager must be initialized first
void ApplyPostproc(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
// Blits the final postprocessed texture to the system framebuffer. The system
// framebuffer is selected as the output buffer. Should be called before
// silhouette rendering.
// @note CPostprocManager must be initialized first
- void ReleaseRenderOutput(
+ void BlitOutputFramebuffer(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::IFramebuffer* destination);
// Returns true if we render main scene in the MSAA framebuffer.
bool IsMultisampleEnabled() const;
// Resolves the MSAA framebuffer into the regular one.
void ResolveMultisampleFramebuffer(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
private:
void CreateMultisampleBuffer();
void DestroyMultisampleBuffer();
std::unique_ptr m_CaptureFramebuffer;
// Two framebuffers, that we flip between at each shader pass.
std::unique_ptr
m_PingFramebuffer, m_PongFramebuffer;
// Unique color textures for the framebuffers.
std::unique_ptr m_ColorTex1, m_ColorTex2;
// The framebuffers share a depth/stencil texture.
std::unique_ptr m_DepthTex;
float m_NearPlane, m_FarPlane;
// A framebuffer and textures x2 for each blur level we render.
struct BlurScale
{
struct Step
{
std::unique_ptr framebuffer;
std::unique_ptr texture;
};
std::array steps;
};
std::array m_BlurScales;
// Indicates which of the ping-pong buffers is used for reading and which for drawing.
bool m_WhichBuffer;
// The name and shader technique we are using. "default" name means no technique is used
// (i.e. while we do allocate the buffers, no effects are rendered).
CStrW m_PostProcEffect;
CShaderTechniquePtr m_PostProcTech;
CStr m_SharpName;
CShaderTechniquePtr m_SharpTech;
float m_Sharpness;
CStr m_AAName;
CShaderTechniquePtr m_AATech;
bool m_UsingMultisampleBuffer;
std::unique_ptr m_MultisampleFramebuffer;
std::unique_ptr
m_MultisampleColorTex, m_MultisampleDepthTex;
uint32_t m_MultisampleCount;
std::vector m_AllowedSampleCounts;
// The current screen dimensions in pixels.
int m_Width, m_Height;
// Is the postproc manager initialized? Buffers created? Default effect loaded?
bool m_IsInitialized;
// Creates blur textures at various scales, for bloom, DOF, etc.
void ApplyBlur(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
// High quality GPU image scaling to half size. outTex must have exactly half the size
// of inTex. inWidth and inHeight are the dimensions of inTex in texels.
void ApplyBlurDownscale2x(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::IFramebuffer* framebuffer,
Renderer::Backend::ITexture* inTex,
int inWidth, int inHeight);
// GPU-based Gaussian blur in two passes. inOutTex contains the input image and will be filled
// with the blurred image. tempTex must have the same size as inOutTex.
// inWidth and inHeight are the dimensions of the images in texels.
void ApplyBlurGauss(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::ITexture* inTex,
Renderer::Backend::ITexture* tempTex,
Renderer::Backend::IFramebuffer* tempFramebuffer,
Renderer::Backend::IFramebuffer* outFramebuffer,
int inWidth, int inHeight);
// Applies a pass of a given effect to the entire current framebuffer. The shader is
// provided with a number of general-purpose variables, including the rendered screen so far,
// the depth buffer, a number of blur textures, the screen size, the zNear/zFar planes and
// some other parameters used by the optional bloom/HDR pass.
void ApplyEffect(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderTechniquePtr& shaderTech, int pass);
// Delete all allocated buffers/textures from GPU memory.
void Cleanup();
// Delete existing buffers/textures and create them again, using a new screen size if needed.
// (the textures are also attached to the framebuffers)
void RecreateBuffers();
};
#endif // INCLUDED_POSTPROCMANAGER
Index: ps/trunk/source/renderer/Renderer.cpp
===================================================================
--- ps/trunk/source/renderer/Renderer.cpp (revision 27312)
+++ ps/trunk/source/renderer/Renderer.cpp (revision 27313)
@@ -1,862 +1,858 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Renderer.h"
#include "graphics/Canvas2D.h"
#include "graphics/CinemaManager.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/ModelDef.h"
#include "graphics/TerrainTextureManager.h"
#include "i18n/L10n.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/tex/tex.h"
#include "gui/GUIManager.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Globals.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Filesystem.h"
#include "ps/World.h"
#include "ps/ProfileViewer.h"
#include "graphics/Camera.h"
#include "graphics/FontManager.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "renderer/backend/IDevice.h"
#include "renderer/DebugRenderer.h"
#include "renderer/PostprocManager.h"
#include "renderer/RenderingOptions.h"
#include "renderer/RenderModifiers.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TimeManager.h"
#include "renderer/VertexBufferManager.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "tools/atlas/GameInterface/View.h"
#include
namespace
{
size_t g_NextScreenShotNumber = 0;
///////////////////////////////////////////////////////////////////////////////////
// CRendererStatsTable - Profile display of rendering stats
/**
* Class CRendererStatsTable: Implementation of AbstractProfileTable to
* display the renderer stats in-game.
*
* Accesses CRenderer::m_Stats by keeping the reference passed to the
* constructor.
*/
class CRendererStatsTable : public AbstractProfileTable
{
NONCOPYABLE(CRendererStatsTable);
public:
CRendererStatsTable(const CRenderer::Stats& st);
// Implementation of AbstractProfileTable interface
CStr GetName();
CStr GetTitle();
size_t GetNumberRows();
const std::vector& GetColumns();
CStr GetCellText(size_t row, size_t col);
AbstractProfileTable* GetChild(size_t row);
private:
/// Reference to the renderer singleton's stats
const CRenderer::Stats& Stats;
/// Column descriptions
std::vector columnDescriptions;
enum
{
Row_DrawCalls = 0,
Row_TerrainTris,
Row_WaterTris,
Row_ModelTris,
Row_OverlayTris,
Row_BlendSplats,
Row_Particles,
Row_VBReserved,
Row_VBAllocated,
Row_TextureMemory,
Row_ShadersLoaded,
// Must be last to count number of rows
NumberRows
};
};
// Construction
CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
: Stats(st)
{
columnDescriptions.push_back(ProfileColumn("Name", 230));
columnDescriptions.push_back(ProfileColumn("Value", 100));
}
// Implementation of AbstractProfileTable interface
CStr CRendererStatsTable::GetName()
{
return "renderer";
}
CStr CRendererStatsTable::GetTitle()
{
return "Renderer statistics";
}
size_t CRendererStatsTable::GetNumberRows()
{
return NumberRows;
}
const std::vector& CRendererStatsTable::GetColumns()
{
return columnDescriptions;
}
CStr CRendererStatsTable::GetCellText(size_t row, size_t col)
{
char buf[256];
switch(row)
{
case Row_DrawCalls:
if (col == 0)
return "# draw calls";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls);
return buf;
case Row_TerrainTris:
if (col == 0)
return "# terrain tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris);
return buf;
case Row_WaterTris:
if (col == 0)
return "# water tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris);
return buf;
case Row_ModelTris:
if (col == 0)
return "# model tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris);
return buf;
case Row_OverlayTris:
if (col == 0)
return "# overlay tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris);
return buf;
case Row_BlendSplats:
if (col == 0)
return "# blend splats";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats);
return buf;
case Row_Particles:
if (col == 0)
return "# particles";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles);
return buf;
case Row_VBReserved:
if (col == 0)
return "VB reserved";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024);
return buf;
case Row_VBAllocated:
if (col == 0)
return "VB allocated";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024);
return buf;
case Row_TextureMemory:
if (col == 0)
return "textures uploaded";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024);
return buf;
case Row_ShadersLoaded:
if (col == 0)
return "shader effects loaded";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded());
return buf;
default:
return "???";
}
}
AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row))
{
return 0;
}
} // anonymous namespace
///////////////////////////////////////////////////////////////////////////////////
// CRenderer implementation
/**
* Struct CRendererInternals: Truly hide data that is supposed to be hidden
* in this structure so it won't even appear in header files.
*/
class CRenderer::Internals
{
NONCOPYABLE(Internals);
public:
std::unique_ptr deviceCommandContext;
/// true if CRenderer::Open has been called
bool IsOpen;
/// true if shaders need to be reloaded
bool ShadersDirty;
/// Table to display renderer stats in-game via profile system
CRendererStatsTable profileTable;
/// Shader manager
CShaderManager shaderManager;
/// Texture manager
CTextureManager textureManager;
/// Time manager
CTimeManager timeManager;
/// Postprocessing effect manager
CPostprocManager postprocManager;
CSceneRenderer sceneRenderer;
CDebugRenderer debugRenderer;
CFontManager fontManager;
Internals() :
IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats),
deviceCommandContext(g_VideoMode.GetBackendDevice()->CreateCommandContext()),
textureManager(g_VFS, false, g_VideoMode.GetBackendDevice())
{
}
};
CRenderer::CRenderer()
{
TIMER(L"InitRenderer");
m = std::make_unique();
g_ProfileViewer.AddRootTable(&m->profileTable);
m_Width = 0;
m_Height = 0;
m_Stats.Reset();
// Create terrain related stuff.
new CTerrainTextureManager;
Open(g_xres, g_yres);
// Setup lighting environment. Since the Renderer accesses the
// lighting environment through a pointer, this has to be done before
// the first Frame.
GetSceneRenderer().SetLightEnv(&g_LightEnv);
- // I haven't seen the camera affecting GUI rendering and such, but the
- // viewport has to be updated according to the video mode
- SViewPort vp;
- vp.m_X = 0;
- vp.m_Y = 0;
- vp.m_Width = g_xres;
- vp.m_Height = g_yres;
- SetViewport(vp);
ModelDefActivateFastImpl();
ColorActivateFastImpl();
ModelRenderer::Init();
}
CRenderer::~CRenderer()
{
delete &g_TexMan;
// We no longer UnloadWaterTextures here -
// that is the responsibility of the module that asked for
// them to be loaded (i.e. CGameView).
m.reset();
}
void CRenderer::ReloadShaders()
{
ENSURE(m->IsOpen);
m->sceneRenderer.ReloadShaders();
m->ShadersDirty = false;
}
bool CRenderer::Open(int width, int height)
{
m->IsOpen = true;
// Dimensions
m_Width = width;
m_Height = height;
// Validate the currently selected render path
SetRenderPath(g_RenderingOptions.GetRenderPath());
if (m->postprocManager.IsEnabled())
m->postprocManager.Initialize();
m->sceneRenderer.Initialize();
return true;
}
void CRenderer::Resize(int width, int height)
{
m_Width = width;
m_Height = height;
m->postprocManager.Resize();
m->sceneRenderer.Resize(width, height);
}
void CRenderer::SetRenderPath(RenderPath rp)
{
if (!m->IsOpen)
{
// Delay until Open() is called.
return;
}
// Renderer has been opened, so validate the selected renderpath
const bool hasShadersSupport =
g_VideoMode.GetBackendDevice()->GetCapabilities().ARBShaders ||
g_VideoMode.GetBackendDevice()->GetBackend() != Renderer::Backend::Backend::GL_ARB;
if (rp == RenderPath::DEFAULT)
{
if (hasShadersSupport)
rp = RenderPath::SHADER;
else
rp = RenderPath::FIXED;
}
if (rp == RenderPath::SHADER)
{
if (!hasShadersSupport)
{
LOGWARNING("Falling back to fixed function\n");
rp = RenderPath::FIXED;
}
}
// TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere.
g_RenderingOptions.m_RenderPath = rp;
MakeShadersDirty();
}
bool CRenderer::ShouldRender() const
{
return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
}
void CRenderer::RenderFrame(const bool needsPresent)
{
// Do not render if not focused while in fullscreen or minimised,
// as that triggers a difficult-to-reproduce crash on some graphic cards.
if (!ShouldRender())
return;
if (m_ScreenShotType == ScreenShotType::BIG)
{
RenderBigScreenShot(needsPresent);
}
else if (m_ScreenShotType == ScreenShotType::DEFAULT)
{
RenderScreenShot(needsPresent);
}
else
{
if (needsPresent)
{
// In case of no acquired backbuffer we have nothing render to.
if (!g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
return;
}
if (m_ShouldPreloadResourcesBeforeNextFrame)
{
m_ShouldPreloadResourcesBeforeNextFrame = false;
// We don't need to render logger for the preload.
RenderFrameImpl(true, false);
}
RenderFrameImpl(true, true);
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
}
}
void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
{
PROFILE3("render");
g_Profiler2.RecordGPUFrameStart();
g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get());
m->textureManager.MakeUploadProgress(m->deviceCommandContext.get());
// prepare before starting the renderer frame
if (g_Game && g_Game->IsGameStarted())
g_Game->GetView()->BeginFrame();
if (g_Game)
m->sceneRenderer.SetSimulation(g_Game->GetSimulation2());
// start new frame
BeginFrame();
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->Prepare(m->deviceCommandContext.get());
m->deviceCommandContext->SetGraphicsPipelineState(
Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
+ Renderer::Backend::IFramebuffer* framebuffer = nullptr;
+
CPostprocManager& postprocManager = g_Renderer.GetPostprocManager();
if (postprocManager.IsEnabled())
{
// We have to update the post process manager with real near/far planes
// that we use for the scene rendering.
postprocManager.SetDepthBufferClipPlanes(
m->sceneRenderer.GetViewCamera().GetNearPlane(),
m->sceneRenderer.GetViewCamera().GetFarPlane()
);
postprocManager.Initialize();
- postprocManager.CaptureRenderOutput(m->deviceCommandContext.get());
+ framebuffer = postprocManager.PrepareAndGetOutputFramebuffer();
}
else
{
// We don't need to clear the color attachment of the framebuffer as the sky
// is going to be rendered anyway.
- m->deviceCommandContext->BeginFramebufferPass(
+ framebuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::CLEAR,
- Renderer::Backend::AttachmentStoreOp::DONT_CARE));
+ Renderer::Backend::AttachmentStoreOp::DONT_CARE);
}
+ m->deviceCommandContext->BeginFramebufferPass(framebuffer);
+
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = framebuffer->GetWidth();
+ viewportRect.height = framebuffer->GetHeight();
+ m->deviceCommandContext->SetViewports(1, &viewportRect);
+
g_Game->GetView()->Render(m->deviceCommandContext.get());
if (postprocManager.IsEnabled())
{
m->deviceCommandContext->EndFramebufferPass();
if (postprocManager.IsMultisampleEnabled())
postprocManager.ResolveMultisampleFramebuffer(m->deviceCommandContext.get());
postprocManager.ApplyPostproc(m->deviceCommandContext.get());
Renderer::Backend::IFramebuffer* backbuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::LOAD,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::LOAD,
Renderer::Backend::AttachmentStoreOp::DONT_CARE);
- postprocManager.ReleaseRenderOutput(
+ postprocManager.BlitOutputFramebuffer(
m->deviceCommandContext.get(), backbuffer);
m->deviceCommandContext->BeginFramebufferPass(backbuffer);
+
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = backbuffer->GetWidth();
+ viewportRect.height = backbuffer->GetHeight();
+ m->deviceCommandContext->SetViewports(1, &viewportRect);
}
g_Game->GetView()->RenderOverlays(m->deviceCommandContext.get());
g_Game->GetView()->GetCinema()->Render();
}
else
{
// We have a fullscreen background in our UI so we don't need
// to clear the color attachment.
// We don't need a depth test to render so we don't care about the
// depth-stencil attachment content.
// In case of Atlas we don't have g_Game, so we still need to clear depth.
const Renderer::Backend::AttachmentLoadOp depthStencilLoadOp =
g_AtlasGameLoop && g_AtlasGameLoop->view
? Renderer::Backend::AttachmentLoadOp::CLEAR
: Renderer::Backend::AttachmentLoadOp::DONT_CARE;
- m->deviceCommandContext->BeginFramebufferPass(
+ Renderer::Backend::IFramebuffer* backbuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
depthStencilLoadOp,
- Renderer::Backend::AttachmentStoreOp::DONT_CARE));
+ Renderer::Backend::AttachmentStoreOp::DONT_CARE);
+ m->deviceCommandContext->BeginFramebufferPass(backbuffer);
+
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = backbuffer->GetWidth();
+ viewportRect.height = backbuffer->GetHeight();
+ m->deviceCommandContext->SetViewports(1, &viewportRect);
}
// If we're in Atlas game view, render special tools
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
g_AtlasGameLoop->view->DrawCinemaPathTool();
}
RenderFrame2D(renderGUI, renderLogger);
m->deviceCommandContext->EndFramebufferPass();
EndFrame();
const Stats& stats = GetStats();
PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls);
PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris);
PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris);
PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris);
PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris);
PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats);
PROFILE2_ATTR("particles: %zu", stats.m_Particles);
g_Profiler2.RecordGPUFrameEnd();
}
void CRenderer::RenderFrame2D(const bool renderGUI, const bool renderLogger)
{
CCanvas2D canvas(g_xres, g_yres, g_VideoMode.GetScale(), m->deviceCommandContext.get());
m->sceneRenderer.RenderTextOverlays(canvas);
if (renderGUI)
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render GUI");
// All GUI elements are drawn in Z order to render semi-transparent
// objects correctly.
g_GUI->Draw(canvas);
}
// If we're in Atlas game view, render special overlays (e.g. editor bandbox).
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
g_AtlasGameLoop->view->DrawOverlays(canvas);
}
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render console");
g_Console->Render(canvas);
}
if (renderLogger)
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render logger");
g_Logger->Render(canvas);
}
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render profiler");
// Profile information
g_ProfileViewer.RenderProfile(canvas);
}
}
void CRenderer::RenderScreenShot(const bool needsPresent)
{
m_ScreenShotType = ScreenShotType::NONE;
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath filenameFormat(L"screenshots/screenshot%04d.png");
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
const size_t width = static_cast(g_xres), height = static_cast(g_yres);
const size_t bpp = 24;
if (needsPresent && !g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
return;
// Hide log messages and re-render
RenderFrameImpl(true, false);
const size_t img_size = width * height * bpp / 8;
const size_t hdr_size = tex_hdr_size(filename);
std::shared_ptr buf;
AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
void* img = buf.get() + hdr_size;
Tex t;
if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0)
return;
m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img);
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
}
void CRenderer::RenderBigScreenShot(const bool needsPresent)
{
m_ScreenShotType = ScreenShotType::NONE;
// If the game hasn't started yet then use WriteScreenshot to generate the image.
if (!g_Game)
return RenderScreenShot(needsPresent);
int tiles = 4, tileWidth = 256, tileHeight = 256;
CFG_GET_VAL("screenshot.tiles", tiles);
CFG_GET_VAL("screenshot.tilewidth", tileWidth);
CFG_GET_VAL("screenshot.tileheight", tileHeight);
if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0)
{
LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight);
return;
}
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp");
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
// Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
// hope the screen is actually large enough for that.
ENSURE(g_xres >= tileWidth && g_yres >= tileHeight);
const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles;
const int bpp = 24;
const size_t imageSize = imageWidth * imageHeight * bpp / 8;
const size_t tileSize = tileWidth * tileHeight * bpp / 8;
const size_t headerSize = tex_hdr_size(filename);
void* tileData = malloc(tileSize);
if (!tileData)
{
WARN_IF_ERR(ERR::NO_MEM);
return;
}
std::shared_ptr imageBuffer;
AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize);
Tex t;
void* img = imageBuffer.get() + headerSize;
if (t.wrap(imageWidth, imageHeight, bpp, TEX_BOTTOM_UP, imageBuffer, headerSize) < 0)
{
free(tileData);
return;
}
CCamera oldCamera = *g_Game->GetView()->GetCamera();
// Resize various things so that the sizes and aspect ratios are correct
{
g_Renderer.Resize(tileWidth, tileHeight);
SViewPort vp = { 0, 0, tileWidth, tileHeight };
g_Game->GetView()->SetViewport(vp);
}
// Render each tile
CMatrix3D projection;
projection.SetIdentity();
const float aspectRatio = 1.0f * tileWidth / tileHeight;
for (int tileY = 0; tileY < tiles; ++tileY)
{
for (int tileX = 0; tileX < tiles; ++tileX)
{
// Adjust the camera to render the appropriate region
if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY);
}
g_Game->GetView()->GetCamera()->SetProjection(projection);
if (!needsPresent || g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
{
RenderFrameImpl(false, false);
m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData);
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
}
// Copy the tile pixels into the main image
for (int y = 0; y < tileHeight; ++y)
{
void* dest = static_cast(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8;
void* src = static_cast(tileData) + y * tileWidth * bpp / 8;
memcpy(dest, src, tileWidth * bpp / 8);
}
}
}
// Restore the viewport settings
{
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->SetViewport(vp);
g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
}
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
free(tileData);
}
void CRenderer::BeginFrame()
{
PROFILE("begin frame");
// Zero out all the per-frame stats.
m_Stats.Reset();
if (m->ShadersDirty)
ReloadShaders();
m->sceneRenderer.BeginFrame();
}
void CRenderer::EndFrame()
{
PROFILE3("end frame");
m->sceneRenderer.EndFrame();
}
-void CRenderer::SetViewport(const SViewPort &vp)
-{
- m_Viewport = vp;
- Renderer::Backend::IDeviceCommandContext::Rect viewportRect;
- viewportRect.x = vp.m_X;
- viewportRect.y = vp.m_Y;
- viewportRect.width = vp.m_Width;
- viewportRect.height = vp.m_Height;
- m->deviceCommandContext->SetViewports(1, &viewportRect);
-}
-
-SViewPort CRenderer::GetViewport()
-{
- return m_Viewport;
-}
-
void CRenderer::MakeShadersDirty()
{
m->ShadersDirty = true;
m->sceneRenderer.MakeShadersDirty();
}
CTextureManager& CRenderer::GetTextureManager()
{
return m->textureManager;
}
CShaderManager& CRenderer::GetShaderManager()
{
return m->shaderManager;
}
CTimeManager& CRenderer::GetTimeManager()
{
return m->timeManager;
}
CPostprocManager& CRenderer::GetPostprocManager()
{
return m->postprocManager;
}
CSceneRenderer& CRenderer::GetSceneRenderer()
{
return m->sceneRenderer;
}
CDebugRenderer& CRenderer::GetDebugRenderer()
{
return m->debugRenderer;
}
CFontManager& CRenderer::GetFontManager()
{
return m->fontManager;
}
void CRenderer::PreloadResourcesBeforeNextFrame()
{
m_ShouldPreloadResourcesBeforeNextFrame = true;
}
void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType)
{
m_ScreenShotType = screenShotType;
}
Renderer::Backend::IDeviceCommandContext* CRenderer::GetDeviceCommandContext()
{
return m->deviceCommandContext.get();
}
Index: ps/trunk/source/renderer/Renderer.h
===================================================================
--- ps/trunk/source/renderer/Renderer.h (revision 27312)
+++ ps/trunk/source/renderer/Renderer.h (revision 27313)
@@ -1,181 +1,173 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER
#define INCLUDED_RENDERER
#include "graphics/Camera.h"
#include "graphics/ShaderDefines.h"
#include "graphics/ShaderProgramPtr.h"
#include "ps/Singleton.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/RenderingOptions.h"
#include "renderer/Scene.h"
#include
class CDebugRenderer;
class CFontManager;
class CPostprocManager;
class CSceneRenderer;
class CShaderManager;
class CTextureManager;
class CTimeManager;
#define g_Renderer CRenderer::GetSingleton()
/**
* Higher level interface on top of the whole frame rendering. It does know
* what should be rendered and via which renderer but shouldn't know how to
* render a particular area, like UI or scene.
*/
class CRenderer : public Singleton
{
public:
// stats class - per frame counts of number of draw calls, poly counts etc
struct Stats
{
// set all stats to zero
void Reset() { memset(this, 0, sizeof(*this)); }
// number of draw calls per frame - total DrawElements + Begin/End immediate mode loops
size_t m_DrawCalls;
// number of terrain triangles drawn
size_t m_TerrainTris;
// number of water triangles drawn
size_t m_WaterTris;
// number of (non-transparent) model triangles drawn
size_t m_ModelTris;
// number of overlay triangles drawn
size_t m_OverlayTris;
// number of splat passes for alphamapping
size_t m_BlendSplats;
// number of particles
size_t m_Particles;
};
enum class ScreenShotType
{
NONE,
DEFAULT,
BIG
};
public:
CRenderer();
~CRenderer();
// open up the renderer: performs any necessary initialisation
bool Open(int width, int height);
// resize renderer view
void Resize(int width, int height);
// return view width
int GetWidth() const { return m_Width; }
// return view height
int GetHeight() const { return m_Height; }
void RenderFrame(bool needsPresent);
// signal frame start
void BeginFrame();
// signal frame end
void EndFrame();
// trigger a reload of shaders (when parameters they depend on have changed)
void MakeShadersDirty();
- // set the viewport
- void SetViewport(const SViewPort &);
-
- // get the last viewport
- SViewPort GetViewport();
-
// return stats accumulated for current frame
Stats& GetStats() { return m_Stats; }
CTextureManager& GetTextureManager();
CShaderManager& GetShaderManager();
CFontManager& GetFontManager();
CTimeManager& GetTimeManager();
CPostprocManager& GetPostprocManager();
CSceneRenderer& GetSceneRenderer();
CDebugRenderer& GetDebugRenderer();
/**
* Performs a complete frame without presenting to force loading all needed
* resources. It's used for the first frame on a game start.
* TODO: It might be better to preload resources without a complete frame
* rendering.
*/
void PreloadResourcesBeforeNextFrame();
/**
* Makes a screenshot on the next RenderFrame according of the given
* screenshot type.
*/
void MakeScreenShotOnNextFrame(ScreenShotType screenShotType);
Renderer::Backend::IDeviceCommandContext* GetDeviceCommandContext();
protected:
friend class CPatchRData;
friend class CDecalRData;
friend class HWLightingModelRenderer;
friend class ShaderModelVertexRenderer;
friend class InstancingModelRenderer;
friend class CRenderingOptions;
bool ShouldRender() const;
void RenderFrameImpl(const bool renderGUI, const bool renderLogger);
void RenderFrame2D(const bool renderGUI, const bool renderLogger);
void RenderScreenShot(const bool needsPresent);
void RenderBigScreenShot(const bool needsPresent);
// SetRenderPath: Select the preferred render path.
// This may only be called before Open(), because the layout of vertex arrays and other
// data may depend on the chosen render path.
void SetRenderPath(RenderPath rp);
void ReloadShaders();
// Private data that is not needed by inline functions.
class Internals;
std::unique_ptr m;
// view width
int m_Width = 0;
// view height
int m_Height = 0;
- SViewPort m_Viewport;
-
// per-frame renderer stats
Stats m_Stats;
bool m_ShouldPreloadResourcesBeforeNextFrame = false;
ScreenShotType m_ScreenShotType = ScreenShotType::NONE;
};
#endif // INCLUDED_RENDERER
Index: ps/trunk/source/renderer/SceneRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/SceneRenderer.cpp (revision 27312)
+++ ps/trunk/source/renderer/SceneRenderer.cpp (revision 27313)
@@ -1,1194 +1,1193 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "SceneRenderer.h"
#include "graphics/Camera.h"
#include "graphics/Decal.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/LOSTexture.h"
#include "graphics/MaterialManager.h"
#include "graphics/MiniMapTexture.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/ParticleManager.h"
#include "graphics/Patch.h"
#include "graphics/ShaderManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/Terrain.h"
#include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "maths/Matrix3D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/backend/IDevice.h"
#include "renderer/DebugRenderer.h"
#include "renderer/HWLightingModelRenderer.h"
#include "renderer/InstancingModelRenderer.h"
#include "renderer/ModelRenderer.h"
#include "renderer/OverlayRenderer.h"
#include "renderer/ParticleRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/RenderModifiers.h"
#include "renderer/ShadowMap.h"
#include "renderer/SilhouetteRenderer.h"
#include "renderer/SkyManager.h"
#include "renderer/TerrainOverlay.h"
#include "renderer/TerrainRenderer.h"
#include "renderer/WaterManager.h"
#include
struct SScreenRect
{
int x1, y1, x2, y2;
};
/**
* Struct CSceneRendererInternals: Truly hide data that is supposed to be hidden
* in this structure so it won't even appear in header files.
*/
class CSceneRenderer::Internals
{
NONCOPYABLE(Internals);
public:
Internals() = default;
~Internals() = default;
/// Water manager
WaterManager waterManager;
/// Sky manager
SkyManager skyManager;
/// Terrain renderer
TerrainRenderer terrainRenderer;
/// Overlay renderer
OverlayRenderer overlayRenderer;
/// Particle manager
CParticleManager particleManager;
/// Particle renderer
ParticleRenderer particleRenderer;
/// Material manager
CMaterialManager materialManager;
/// Shadow map
ShadowMap shadow;
SilhouetteRenderer silhouetteRenderer;
/// Various model renderers
struct Models
{
// NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer,
// RenderModifier, etc) is mostly a relic of an older design that implemented
// the different materials and rendering modes through extensive subclassing
// and hooking objects together in various combinations.
// The new design uses the CShaderManager API to abstract away the details
// of rendering, and uses a data-driven approach to materials, so there are
// now a small number of generic subclasses instead of many specialised subclasses,
// but most of the old infrastructure hasn't been refactored out yet and leads to
// some unwanted complexity.
// Submitted models are split on two axes:
// - Normal vs Transp[arent] - alpha-blended models are stored in a separate
// list so we can draw them above/below the alpha-blended water plane correctly
// - Skinned vs Unskinned - with hardware lighting we don't need to
// duplicate mesh data per model instance (except for skinned models),
// so non-skinned models get different ModelVertexRenderers
ModelRendererPtr NormalSkinned;
ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported
ModelRendererPtr TranspSkinned;
ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported
ModelVertexRendererPtr VertexRendererShader;
ModelVertexRendererPtr VertexInstancingShader;
ModelVertexRendererPtr VertexGPUSkinningShader;
LitRenderModifierPtr ModShader;
} Model;
CShaderDefines globalContext;
/**
* Renders all non-alpha-blended models with the given context.
*/
void CallModelRenderers(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags)
{
CShaderDefines contextSkinned = context;
if (g_RenderingOptions.GetGPUSkinning())
{
contextSkinned.Add(str_USE_INSTANCING, str_1);
contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
}
Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags);
if (Model.NormalUnskinned != Model.NormalSkinned)
{
CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags);
}
}
/**
* Renders all alpha-blended models with the given context.
*/
void CallTranspModelRenderers(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags)
{
CShaderDefines contextSkinned = context;
if (g_RenderingOptions.GetGPUSkinning())
{
contextSkinned.Add(str_USE_INSTANCING, str_1);
contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
}
Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags);
if (Model.TranspUnskinned != Model.TranspSkinned)
{
CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags);
}
}
};
CSceneRenderer::CSceneRenderer()
{
m = std::make_unique();
m_TerrainRenderMode = SOLID;
m_WaterRenderMode = SOLID;
m_ModelRenderMode = SOLID;
m_OverlayRenderMode = SOLID;
m_DisplayTerrainPriorities = false;
m_LightEnv = nullptr;
m_CurrentScene = nullptr;
}
CSceneRenderer::~CSceneRenderer()
{
// We no longer UnloadWaterTextures here -
// that is the responsibility of the module that asked for
// them to be loaded (i.e. CGameView).
m.reset();
}
void CSceneRenderer::ReloadShaders()
{
m->globalContext = CShaderDefines();
Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice();
if (g_RenderingOptions.GetShadows())
{
m->globalContext.Add(str_USE_SHADOW, str_1);
if (device->GetBackend() == Renderer::Backend::Backend::GL_ARB &&
device->GetCapabilities().ARBShadersShadow)
{
m->globalContext.Add(str_USE_FP_SHADOW, str_1);
}
if (g_RenderingOptions.GetShadowPCF())
m->globalContext.Add(str_USE_SHADOW_PCF, str_1);
const int cascadeCount = m->shadow.GetCascadeCount();
ENSURE(1 <= cascadeCount && cascadeCount <= 4);
const CStrIntern cascadeCountStr[5] = {str_0, str_1, str_2, str_3, str_4};
m->globalContext.Add(str_SHADOWS_CASCADE_COUNT, cascadeCountStr[cascadeCount]);
#if !CONFIG2_GLES
m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1);
#endif
}
m->globalContext.Add(str_RENDER_DEBUG_MODE,
RenderDebugModeEnum::ToString(g_RenderingOptions.GetRenderDebugMode()));
if (device->GetBackend() != Renderer::Backend::Backend::GL_ARB && g_RenderingOptions.GetFog())
m->globalContext.Add(str_USE_FOG, str_1);
m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier());
ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED);
m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer());
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, device->GetBackend() != Renderer::Backend::Backend::GL_ARB));
if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too
{
m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, device->GetBackend() != Renderer::Backend::Backend::GL_ARB));
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
}
else
{
m->Model.VertexGPUSkinningShader.reset();
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
}
m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
}
void CSceneRenderer::Initialize()
{
// Let component renderers perform one-time initialization after graphics capabilities and
// the shader path have been determined.
m->overlayRenderer.Initialize();
}
// resize renderer view
void CSceneRenderer::Resize(int UNUSED(width), int UNUSED(height))
{
// need to recreate the shadow map object to resize the shadow texture
m->shadow.RecreateTexture();
m->waterManager.RecreateOrLoadTexturesIfNeeded();
}
void CSceneRenderer::BeginFrame()
{
// choose model renderers for this frame
m->Model.ModShader->SetShadowMap(&m->shadow);
m->Model.ModShader->SetLightEnv(m_LightEnv);
}
void CSceneRenderer::SetSimulation(CSimulation2* simulation)
{
// set current simulation context for terrain renderer
m->terrainRenderer.SetSimulation(simulation);
}
void CSceneRenderer::RenderShadowMap(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context)
{
PROFILE3_GPU("shadow map");
GPU_SCOPED_LABEL(deviceCommandContext, "Render shadow map");
CShaderDefines shadowsContext = context;
shadowsContext.Add(str_PASS_SHADOWS, str_1);
CShaderDefines contextCast = shadowsContext;
contextCast.Add(str_MODE_SHADOWCAST, str_1);
m->shadow.BeginRender(deviceCommandContext);
const int cascadeCount = m->shadow.GetCascadeCount();
ENSURE(0 <= cascadeCount && cascadeCount <= 4);
for (int cascade = 0; cascade < cascadeCount; ++cascade)
{
m->shadow.PrepareCamera(deviceCommandContext, cascade);
const int cullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
{
PROFILE("render patches");
m->terrainRenderer.RenderPatches(deviceCommandContext, cullGroup, shadowsContext);
}
{
PROFILE("render models");
m->CallModelRenderers(deviceCommandContext, contextCast, cullGroup, MODELFLAG_CASTSHADOWS);
}
{
PROFILE("render transparent models");
m->CallTranspModelRenderers(deviceCommandContext, contextCast, cullGroup, MODELFLAG_CASTSHADOWS);
}
}
m->shadow.EndRender(deviceCommandContext);
-
- g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
}
void CSceneRenderer::RenderPatches(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup)
{
PROFILE3_GPU("patches");
GPU_SCOPED_LABEL(deviceCommandContext, "Render patches");
// Switch on wireframe if we need it.
CShaderDefines localContext = context;
if (m_TerrainRenderMode == WIREFRAME)
localContext.Add(str_MODE_WIREFRAME, str_1);
// Render all the patches, including blend pass.
m->terrainRenderer.RenderTerrainShader(deviceCommandContext, localContext, cullGroup,
g_RenderingOptions.GetShadows() ? &m->shadow : nullptr);
if (m_TerrainRenderMode == EDGED_FACES)
{
localContext.Add(str_MODE_WIREFRAME, str_1);
// Edged faces: need to make a second pass over the data.
// Render tiles edges.
m->terrainRenderer.RenderPatches(
deviceCommandContext, cullGroup, localContext, CColor(0.5f, 0.5f, 1.0f, 1.0f));
// Render outline of each patch.
m->terrainRenderer.RenderOutlines(deviceCommandContext, cullGroup);
}
}
void CSceneRenderer::RenderModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup)
{
PROFILE3_GPU("models");
GPU_SCOPED_LABEL(deviceCommandContext, "Render models");
int flags = 0;
CShaderDefines localContext = context;
if (m_ModelRenderMode == WIREFRAME)
localContext.Add(str_MODE_WIREFRAME, str_1);
m->CallModelRenderers(deviceCommandContext, localContext, cullGroup, flags);
if (m_ModelRenderMode == EDGED_FACES)
{
localContext.Add(str_MODE_WIREFRAME_SOLID, str_1);
m->CallModelRenderers(deviceCommandContext, localContext, cullGroup, flags);
}
}
void CSceneRenderer::RenderTransparentModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode)
{
PROFILE3_GPU("transparent models");
GPU_SCOPED_LABEL(deviceCommandContext, "Render transparent models");
int flags = 0;
CShaderDefines contextOpaque = context;
contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1);
CShaderDefines contextBlend = context;
contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1);
if (m_ModelRenderMode == WIREFRAME)
{
contextOpaque.Add(str_MODE_WIREFRAME, str_1);
contextBlend.Add(str_MODE_WIREFRAME, str_1);
}
if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE)
m->CallTranspModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags);
if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND)
m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags);
if (m_ModelRenderMode == EDGED_FACES)
{
CShaderDefines contextWireframe = contextOpaque;
contextWireframe.Add(str_MODE_WIREFRAME, str_1);
m->CallTranspModelRenderers(deviceCommandContext, contextWireframe, cullGroup, flags);
}
}
// SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space)
// Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html
// - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test)
void CSceneRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const
{
// First, we'll convert the given clip plane to camera space, then we'll
// Get the view matrix and normal matrix (top 3x3 part of view matrix)
CMatrix3D normalMatrix = camera.GetOrientation().GetTranspose();
CVector4D camPlane = normalMatrix.Transform(worldPlane);
CMatrix3D matrix = camera.GetProjection();
// Calculate the clip-space corner point opposite the clipping plane
// as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and
// transform it into camera space by multiplying it
// by the inverse of the projection matrix
CVector4D q;
q.X = (Sign(camPlane.X) - matrix[8] / matrix[11]) / matrix[0];
q.Y = (Sign(camPlane.Y) - matrix[9] / matrix[11]) / matrix[5];
q.Z = 1.0f / matrix[11];
q.W = (1.0f - matrix[10] / matrix[11]) / matrix[14];
// Calculate the scaled plane vector
CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q));
// Replace the third row of the projection matrix
matrix[2] = c.X;
matrix[6] = c.Y;
matrix[10] = c.Z - matrix[11];
matrix[14] = c.W;
// Load it back into the camera
camera.SetProjection(matrix);
}
void CSceneRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
{
WaterManager& wm = m->waterManager;
CMatrix3D projection;
if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
const float aspectRatio = 1.0f;
// Expand fov slightly since ripples can reflect parts of the scene that
// are slightly outside the normal camera view, and we want to avoid any
// noticeable edge-filtering artifacts
projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane());
}
else
projection = m_ViewCamera.GetProjection();
camera = m_ViewCamera;
// Temporarily change the camera to one that is reflected.
// Also, for texturing purposes, make it render to a view port the size of the
// water texture, stretch the image according to our aspect ratio so it covers
// the whole screen despite being rendered into a square, and cover slightly more
// of the view so we can see wavy reflections of slightly off-screen objects.
camera.m_Orientation.Scale(1, -1, 1);
camera.m_Orientation.Translate(0, 2 * wm.m_WaterHeight, 0);
camera.UpdateFrustum(scissor);
// Clip slightly above the water to improve reflections of objects on the water
// when the reflections are distorted.
camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight + 2.0f));
SViewPort vp;
vp.m_Height = wm.m_RefTextureSize;
vp.m_Width = wm.m_RefTextureSize;
vp.m_X = 0;
vp.m_Y = 0;
camera.SetViewPort(vp);
camera.SetProjection(projection);
CMatrix3D scaleMat;
scaleMat.SetScaling(g_Renderer.GetHeight() / static_cast(std::max(1, g_Renderer.GetWidth())), 1.0f, 1.0f);
camera.SetProjection(scaleMat * camera.GetProjection());
CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f);
SetObliqueFrustumClipping(camera, camPlane);
}
void CSceneRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
{
WaterManager& wm = m->waterManager;
CMatrix3D projection;
if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
const float aspectRatio = 1.0f;
// Expand fov slightly since ripples can reflect parts of the scene that
// are slightly outside the normal camera view, and we want to avoid any
// noticeable edge-filtering artifacts
projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane());
}
else
projection = m_ViewCamera.GetProjection();
camera = m_ViewCamera;
// Temporarily change the camera to make it render to a view port the size of the
// water texture, stretch the image according to our aspect ratio so it covers
// the whole screen despite being rendered into a square, and cover slightly more
// of the view so we can see wavy refractions of slightly off-screen objects.
camera.UpdateFrustum(scissor);
camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores.
SViewPort vp;
vp.m_Height = wm.m_RefTextureSize;
vp.m_Width = wm.m_RefTextureSize;
vp.m_X = 0;
vp.m_Y = 0;
camera.SetViewPort(vp);
camera.SetProjection(projection);
CMatrix3D scaleMat;
scaleMat.SetScaling(g_Renderer.GetHeight() / static_cast(std::max(1, g_Renderer.GetWidth())), 1.0f, 1.0f);
camera.SetProjection(scaleMat * camera.GetProjection());
}
// RenderReflections: render the water reflections to the reflection texture
void CSceneRenderer::RenderReflections(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, const CBoundingBoxAligned& scissor)
{
PROFILE3_GPU("water reflections");
GPU_SCOPED_LABEL(deviceCommandContext, "Render water reflections");
WaterManager& wm = m->waterManager;
// Remember old camera
CCamera normalCamera = m_ViewCamera;
ComputeReflectionCamera(m_ViewCamera, scissor);
const CBoundingBoxAligned reflectionScissor =
m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
if (reflectionScissor.IsEmpty())
{
m_ViewCamera = normalCamera;
return;
}
- g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
-
// Save the model-view-projection matrix so the shaders can use it for projective texturing
wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection();
float vpHeight = wm.m_RefTextureSize;
float vpWidth = wm.m_RefTextureSize;
SScreenRect screenScissor;
screenScissor.x1 = static_cast(floor((reflectionScissor[0].X * 0.5f + 0.5f) * vpWidth));
screenScissor.y1 = static_cast(floor((reflectionScissor[0].Y * 0.5f + 0.5f) * vpHeight));
screenScissor.x2 = static_cast(ceil((reflectionScissor[1].X * 0.5f + 0.5f) * vpWidth));
screenScissor.y2 = static_cast(ceil((reflectionScissor[1].Y * 0.5f + 0.5f) * vpHeight));
+ deviceCommandContext->SetGraphicsPipelineState(
+ Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
+ deviceCommandContext->BeginFramebufferPass(wm.m_ReflectionFramebuffer.get());
+
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = vpWidth;
+ viewportRect.height = vpHeight;
+ deviceCommandContext->SetViewports(1, &viewportRect);
+
Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
scissorRect.x = screenScissor.x1;
scissorRect.y = screenScissor.y1;
scissorRect.width = screenScissor.x2 - screenScissor.x1;
scissorRect.height = screenScissor.y2 - screenScissor.y1;
deviceCommandContext->SetScissors(1, &scissorRect);
- deviceCommandContext->SetGraphicsPipelineState(
- Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
- deviceCommandContext->BeginFramebufferPass(wm.m_ReflectionFramebuffer.get());
-
CShaderDefines reflectionsContext = context;
reflectionsContext.Add(str_PASS_REFLECTIONS, str_1);
// Render terrain and models
RenderPatches(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS);
RenderModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS);
RenderTransparentModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS, TRANSPARENT);
// Particles are always oriented to face the camera in the vertex shader,
// so they don't need the inverted cull face.
if (g_RenderingOptions.GetParticles())
{
RenderParticles(deviceCommandContext, CULL_REFLECTIONS);
}
deviceCommandContext->SetScissors(0, nullptr);
deviceCommandContext->EndFramebufferPass();
// Reset old camera
m_ViewCamera = normalCamera;
- g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
}
// RenderRefractions: render the water refractions to the refraction texture
void CSceneRenderer::RenderRefractions(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, const CBoundingBoxAligned &scissor)
{
PROFILE3_GPU("water refractions");
GPU_SCOPED_LABEL(deviceCommandContext, "Render water refractions");
WaterManager& wm = m->waterManager;
// Remember old camera
CCamera normalCamera = m_ViewCamera;
ComputeRefractionCamera(m_ViewCamera, scissor);
const CBoundingBoxAligned refractionScissor =
m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
if (refractionScissor.IsEmpty())
{
m_ViewCamera = normalCamera;
return;
}
CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f);
SetObliqueFrustumClipping(m_ViewCamera, camPlane);
- g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
-
// Save the model-view-projection matrix so the shaders can use it for projective texturing
wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection();
wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse();
wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation();
float vpHeight = wm.m_RefTextureSize;
float vpWidth = wm.m_RefTextureSize;
SScreenRect screenScissor;
screenScissor.x1 = static_cast(floor((refractionScissor[0].X * 0.5f + 0.5f) * vpWidth));
screenScissor.y1 = static_cast(floor((refractionScissor[0].Y * 0.5f + 0.5f) * vpHeight));
screenScissor.x2 = static_cast(ceil((refractionScissor[1].X * 0.5f + 0.5f) * vpWidth));
screenScissor.y2 = static_cast(ceil((refractionScissor[1].Y * 0.5f + 0.5f) * vpHeight));
+ deviceCommandContext->SetGraphicsPipelineState(
+ Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
+ deviceCommandContext->BeginFramebufferPass(wm.m_RefractionFramebuffer.get());
+
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = vpWidth;
+ viewportRect.height = vpHeight;
+ deviceCommandContext->SetViewports(1, &viewportRect);
+
Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
scissorRect.x = screenScissor.x1;
scissorRect.y = screenScissor.y1;
scissorRect.width = screenScissor.x2 - screenScissor.x1;
scissorRect.height = screenScissor.y2 - screenScissor.y1;
deviceCommandContext->SetScissors(1, &scissorRect);
- deviceCommandContext->SetGraphicsPipelineState(
- Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
- deviceCommandContext->BeginFramebufferPass(wm.m_RefractionFramebuffer.get());
-
// Render terrain and models
RenderPatches(deviceCommandContext, context, CULL_REFRACTIONS);
// Render debug-related terrain overlays to make it visible under water.
ITerrainOverlay::RenderOverlaysBeforeWater(deviceCommandContext);
RenderModels(deviceCommandContext, context, CULL_REFRACTIONS);
RenderTransparentModels(deviceCommandContext, context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE);
deviceCommandContext->SetScissors(0, nullptr);
deviceCommandContext->EndFramebufferPass();
// Reset old camera
m_ViewCamera = normalCamera;
- g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
}
void CSceneRenderer::RenderSilhouettes(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context)
{
PROFILE3_GPU("silhouettes");
GPU_SCOPED_LABEL(deviceCommandContext, "Render silhouettes");
CShaderDefines contextOccluder = context;
contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1);
CShaderDefines contextDisplay = context;
contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1);
// Render silhouettes of units hidden behind terrain or occluders.
// To avoid breaking the standard rendering of alpha-blended objects, this
// has to be done in a separate pass.
// First we render all occluders into depth, then render all units with
// inverted depth test so any behind an occluder will get drawn in a constant
// color.
deviceCommandContext->SetGraphicsPipelineState(
Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
deviceCommandContext->ClearFramebuffer(false, true, true);
// Render occluders:
{
PROFILE("render patches");
m->terrainRenderer.RenderPatches(deviceCommandContext, CULL_SILHOUETTE_OCCLUDER, contextOccluder);
}
{
PROFILE("render model occluders");
m->CallModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
}
{
PROFILE("render transparent occluders");
m->CallTranspModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
}
// Since we can't sort, we'll use the stencil buffer to ensure we only draw
// a pixel once (using the color of whatever model happens to be drawn first).
{
PROFILE("render model casters");
m->CallModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0);
}
{
PROFILE("render transparent casters");
m->CallTranspModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0);
}
}
void CSceneRenderer::RenderParticles(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
int cullGroup)
{
PROFILE3_GPU("particles");
GPU_SCOPED_LABEL(deviceCommandContext, "Render particles");
m->particleRenderer.RenderParticles(
deviceCommandContext, cullGroup, m_ModelRenderMode == WIREFRAME);
if (m_ModelRenderMode == EDGED_FACES)
{
m->particleRenderer.RenderParticles(
deviceCommandContext, cullGroup, true);
m->particleRenderer.RenderBounds(cullGroup);
}
}
void CSceneRenderer::PrepareSubmissions(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CBoundingBoxAligned& waterScissor)
{
PROFILE3("prepare submissions");
GPU_SCOPED_LABEL(deviceCommandContext, "Prepare submissions");
m->skyManager.LoadAndUploadSkyTexturesIfNeeded(deviceCommandContext);
GetScene().GetLOSTexture().InterpolateLOS(deviceCommandContext);
GetScene().GetTerritoryTexture().UpdateIfNeeded(deviceCommandContext);
GetScene().GetMiniMapTexture().Render(
deviceCommandContext, GetScene().GetLOSTexture(), GetScene().GetTerritoryTexture());
CShaderDefines context = m->globalContext;
- // Set the camera
- g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
-
// Prepare model renderers
{
PROFILE3("prepare models");
m->Model.NormalSkinned->PrepareModels();
m->Model.TranspSkinned->PrepareModels();
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->PrepareModels();
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->PrepareModels();
}
m->terrainRenderer.PrepareForRendering();
m->overlayRenderer.PrepareForRendering();
m->particleRenderer.PrepareForRendering(context);
{
PROFILE3("upload models");
m->Model.NormalSkinned->UploadModels(deviceCommandContext);
m->Model.TranspSkinned->UploadModels(deviceCommandContext);
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->UploadModels(deviceCommandContext);
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->UploadModels(deviceCommandContext);
}
m->overlayRenderer.Upload(deviceCommandContext);
m->particleRenderer.Upload(deviceCommandContext);
if (g_RenderingOptions.GetShadows())
{
RenderShadowMap(deviceCommandContext, context);
}
if (m->waterManager.m_RenderWater)
{
if (waterScissor.GetVolume() > 0 && m->waterManager.WillRenderFancyWater())
{
m->waterManager.UpdateQuality();
PROFILE3_GPU("water scissor");
if (g_RenderingOptions.GetWaterReflection())
RenderReflections(deviceCommandContext, context, waterScissor);
if (g_RenderingOptions.GetWaterRefraction())
RenderRefractions(deviceCommandContext, context, waterScissor);
if (g_RenderingOptions.GetWaterFancyEffects())
m->terrainRenderer.RenderWaterFoamOccluders(deviceCommandContext, CULL_DEFAULT);
}
}
}
void CSceneRenderer::RenderSubmissions(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CBoundingBoxAligned& waterScissor)
{
PROFILE3("render submissions");
GPU_SCOPED_LABEL(deviceCommandContext, "Render submissions");
CShaderDefines context = m->globalContext;
constexpr int cullGroup = CULL_DEFAULT;
m->skyManager.RenderSky(deviceCommandContext);
// render submitted patches and models
RenderPatches(deviceCommandContext, context, cullGroup);
// render debug-related terrain overlays
ITerrainOverlay::RenderOverlaysBeforeWater(deviceCommandContext);
// render other debug-related overlays before water (so they can be seen when underwater)
m->overlayRenderer.RenderOverlaysBeforeWater(deviceCommandContext);
RenderModels(deviceCommandContext, context, cullGroup);
// render water
if (m->waterManager.m_RenderWater && g_Game && waterScissor.GetVolume() > 0)
{
if (m->waterManager.WillRenderFancyWater())
{
// Render transparent stuff, but only the solid parts that can occlude block water.
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT_OPAQUE);
m->terrainRenderer.RenderWater(deviceCommandContext, context, cullGroup, &m->shadow);
// Render transparent stuff again, but only the blended parts that overlap water.
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT_BLEND);
}
else
{
m->terrainRenderer.RenderWater(deviceCommandContext, context, cullGroup, &m->shadow);
// Render transparent stuff, so it can overlap models/terrain.
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT);
}
}
else
{
// render transparent stuff, so it can overlap models/terrain
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT);
}
// render debug-related terrain overlays
ITerrainOverlay::RenderOverlaysAfterWater(deviceCommandContext, cullGroup);
// render some other overlays after water (so they can be displayed on top of water)
m->overlayRenderer.RenderOverlaysAfterWater(deviceCommandContext);
// particles are transparent so render after water
if (g_RenderingOptions.GetParticles())
{
RenderParticles(deviceCommandContext, cullGroup);
}
// render debug lines
if (g_RenderingOptions.GetDisplayFrustum())
DisplayFrustum();
if (g_RenderingOptions.GetDisplayShadowsFrustum())
m->shadow.RenderDebugBounds();
m->silhouetteRenderer.RenderDebugBounds(deviceCommandContext);
}
void CSceneRenderer::EndFrame()
{
// empty lists
m->terrainRenderer.EndFrame();
m->overlayRenderer.EndFrame();
m->particleRenderer.EndFrame();
m->silhouetteRenderer.EndFrame();
// Finish model renderers
m->Model.NormalSkinned->EndFrame();
m->Model.TranspSkinned->EndFrame();
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->EndFrame();
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->EndFrame();
}
void CSceneRenderer::DisplayFrustum()
{
g_Renderer.GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 0.25f), 2);
g_Renderer.GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 1.0f), 2, true);
}
// Text overlay rendering
void CSceneRenderer::RenderTextOverlays(CCanvas2D& canvas)
{
PROFILE3_GPU("text overlays");
if (m_DisplayTerrainPriorities)
m->terrainRenderer.RenderPriorities(canvas, CULL_DEFAULT);
}
// SetSceneCamera: setup projection and transform of camera and adjust viewport to current view
// The camera always represents the actual camera used to render a scene, not any virtual camera
// used for shadow rendering or reflections.
void CSceneRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera)
{
m_ViewCamera = viewCamera;
m_CullCamera = cullCamera;
if (g_RenderingOptions.GetShadows())
m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir());
}
void CSceneRenderer::Submit(CPatch* patch)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
{
m->shadow.AddShadowReceiverBound(patch->GetWorldBounds());
m->silhouetteRenderer.AddOccluder(patch);
}
if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
{
const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
m->shadow.AddShadowCasterBound(cascade, patch->GetWorldBounds());
}
m->terrainRenderer.Submit(m_CurrentCullGroup, patch);
}
void CSceneRenderer::Submit(SOverlayLine* overlay)
{
// Overlays are only needed in the default cull group for now,
// so just ignore submissions to any other group
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlayTexturedLine* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlaySprite* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlayQuad* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlaySphere* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(CModelDecal* decal)
{
// Decals can't cast shadows since they're flat on the terrain.
// They can receive shadows, but the terrain under them will have
// already been passed to AddShadowCasterBound, so don't bother
// doing it again here.
m->terrainRenderer.Submit(m_CurrentCullGroup, decal);
}
void CSceneRenderer::Submit(CParticleEmitter* emitter)
{
m->particleRenderer.Submit(m_CurrentCullGroup, emitter);
}
void CSceneRenderer::SubmitNonRecursive(CModel* model)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
{
m->shadow.AddShadowReceiverBound(model->GetWorldBounds());
if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER)
m->silhouetteRenderer.AddOccluder(model);
if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY)
m->silhouetteRenderer.AddCaster(model);
}
if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
{
if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS))
return;
const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
m->shadow.AddShadowCasterBound(cascade, model->GetWorldBounds());
}
bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0);
if (model->GetMaterial().UsesAlphaBlending())
{
if (requiresSkinning)
m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model);
else
m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model);
}
else
{
if (requiresSkinning)
m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model);
else
m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model);
}
}
void CSceneRenderer::PrepareScene(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Scene& scene)
{
m_CurrentScene = &scene;
CFrustum frustum = m_CullCamera.GetFrustum();
m_CurrentCullGroup = CULL_DEFAULT;
scene.EnumerateObjects(frustum, this);
m->particleManager.RenderSubmit(*this, frustum);
if (g_RenderingOptions.GetSilhouettes())
{
m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera);
m_CurrentCullGroup = CULL_DEFAULT;
m->silhouetteRenderer.RenderSubmitOverlays(*this);
m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER;
m->silhouetteRenderer.RenderSubmitOccluders(*this);
m_CurrentCullGroup = CULL_SILHOUETTE_CASTER;
m->silhouetteRenderer.RenderSubmitCasters(*this);
}
if (g_RenderingOptions.GetShadows())
{
for (int cascade = 0; cascade <= m->shadow.GetCascadeCount(); ++cascade)
{
m_CurrentCullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
const CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(cascade);
scene.EnumerateObjects(shadowFrustum, this);
}
}
if (m->waterManager.m_RenderWater)
{
m_WaterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
if (m_WaterScissor.GetVolume() > 0 && m->waterManager.WillRenderFancyWater())
{
if (g_RenderingOptions.GetWaterReflection())
{
m_CurrentCullGroup = CULL_REFLECTIONS;
CCamera reflectionCamera;
ComputeReflectionCamera(reflectionCamera, m_WaterScissor);
scene.EnumerateObjects(reflectionCamera.GetFrustum(), this);
}
if (g_RenderingOptions.GetWaterRefraction())
{
m_CurrentCullGroup = CULL_REFRACTIONS;
CCamera refractionCamera;
ComputeRefractionCamera(refractionCamera, m_WaterScissor);
scene.EnumerateObjects(refractionCamera.GetFrustum(), this);
}
// Render the waves to the Fancy effects texture
m->waterManager.RenderWaves(deviceCommandContext, frustum);
}
}
else
m_WaterScissor = CBoundingBoxAligned{};
m_CurrentCullGroup = -1;
PrepareSubmissions(deviceCommandContext, m_WaterScissor);
}
void CSceneRenderer::RenderScene(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
ENSURE(m_CurrentScene);
RenderSubmissions(deviceCommandContext, m_WaterScissor);
}
void CSceneRenderer::RenderSceneOverlays(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
if (g_RenderingOptions.GetSilhouettes())
{
RenderSilhouettes(deviceCommandContext, m->globalContext);
}
m->silhouetteRenderer.RenderDebugOverlays(deviceCommandContext);
// Render overlays that should appear on top of all other objects.
m->overlayRenderer.RenderForegroundOverlays(deviceCommandContext, m_ViewCamera);
m_CurrentScene = nullptr;
}
Scene& CSceneRenderer::GetScene()
{
ENSURE(m_CurrentScene);
return *m_CurrentScene;
}
void CSceneRenderer::MakeShadersDirty()
{
m->waterManager.m_NeedsReloading = true;
}
WaterManager& CSceneRenderer::GetWaterManager()
{
return m->waterManager;
}
SkyManager& CSceneRenderer::GetSkyManager()
{
return m->skyManager;
}
CParticleManager& CSceneRenderer::GetParticleManager()
{
return m->particleManager;
}
TerrainRenderer& CSceneRenderer::GetTerrainRenderer()
{
return m->terrainRenderer;
}
CMaterialManager& CSceneRenderer::GetMaterialManager()
{
return m->materialManager;
}
ShadowMap& CSceneRenderer::GetShadowMap()
{
return m->shadow;
}
void CSceneRenderer::ResetState()
{
// Clear all emitters, that were created in previous games
GetParticleManager().ClearUnattachedEmitters();
}
Index: ps/trunk/source/renderer/ShadowMap.cpp
===================================================================
--- ps/trunk/source/renderer/ShadowMap.cpp (revision 27312)
+++ ps/trunk/source/renderer/ShadowMap.cpp (revision 27313)
@@ -1,733 +1,732 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ShadowMap.h"
#include "graphics/Camera.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
#include "lib/bits.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Brush.h"
#include "maths/Frustum.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/ITexture.h"
#include "renderer/DebugRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include
namespace
{
constexpr int MAX_CASCADE_COUNT = 4;
constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f;
constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f;
} // anonymous namespace
/**
* Struct ShadowMapInternals: Internal data for the ShadowMap implementation
*/
struct ShadowMapInternals
{
std::unique_ptr Framebuffer;
std::unique_ptr Texture;
// bit depth for the depth texture
int DepthTextureBits;
// width, height of shadow map
int Width, Height;
// Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
int QualityLevel;
// used width, height of shadow map
int EffectiveWidth, EffectiveHeight;
// Transform world space into light space; calculated on SetupFrame
CMatrix3D LightTransform;
// transform light space into world space
CMatrix3D InvLightTransform;
CBoundingBoxAligned ShadowReceiverBound;
int CascadeCount;
float CascadeDistanceRatio;
float ShadowsCutoffDistance;
bool ShadowsCoverMap;
struct Cascade
{
// transform light space into projected light space
// in projected light space, the shadowbound box occupies the [-1..1] cube
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D LightProjection;
float Distance;
CBoundingBoxAligned FrustumBBAA;
CBoundingBoxAligned ConvexBounds;
CBoundingBoxAligned ShadowRenderBound;
// Bounding box of shadowed objects in the light space.
CBoundingBoxAligned ShadowCasterBound;
// Transform world space into texture space of the shadow map;
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D TextureMatrix;
// View port of the shadow texture where the cascade should be rendered.
SViewPort ViewPort;
};
std::array Cascades;
// Camera transformed into light space
CCamera LightspaceCamera;
// Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
// incorrectly when the FBO has only a depth attachment.
// When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
// alpha texture which is attached to the FBO as a workaround.
std::unique_ptr DummyTexture;
// Copy of renderer's standard view camera, saved between
// BeginRender and EndRender while we replace it with the shadow camera
CCamera SavedViewCamera;
void CalculateShadowMatrices(const int cascade);
void CreateTexture();
void UpdateCascadesParameters();
};
void ShadowMapInternals::UpdateCascadesParameters()
{
CascadeCount = 1;
CFG_GET_VAL("shadowscascadecount", CascadeCount);
if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB)
CascadeCount = 1;
ShadowsCoverMap = false;
CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
}
void CalculateBoundsForCascade(
const CCamera& camera, const CMatrix3D& lightTransform,
const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
CBoundingBoxAligned* frustumBBAA)
{
frustumBBAA->SetEmpty();
// We need to calculate a circumscribed sphere for the camera to
// create a rotation stable bounding box.
const CVector3D cameraIn = camera.m_Orientation.GetIn();
const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation();
const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane;
const CVector3D centerDist = cameraTranslation + cameraIn * farPlane;
// We can solve 3D problem in 2D space, because the frustum is
// symmetric by 2 planes. Than means we can use only one corner
// to find a circumscribed sphere.
CCamera::Quad corners;
camera.GetViewQuad(nearPlane, corners);
for (CVector3D& corner : corners)
corner = camera.GetOrientation().Transform(corner);
const CVector3D cornerNear = corners[0];
for (const CVector3D& corner : corners)
*frustumBBAA += lightTransform.Transform(corner);
camera.GetViewQuad(farPlane, corners);
for (CVector3D& corner : corners)
corner = camera.GetOrientation().Transform(corner);
const CVector3D cornerDist = corners[0];
for (const CVector3D& corner : corners)
*frustumBBAA += lightTransform.Transform(corner);
// We solve 2D case for the right trapezoid.
const float firstBase = (cornerNear - centerNear).Length();
const float secondBase = (cornerDist - centerDist).Length();
const float height = (centerDist - centerNear).Length();
const float distanceToCenter =
(height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
const float radius = (cornerNear - position).Length();
// We need to convert the bounding box to the light space.
position = lightTransform.Rotate(position);
const float insets = 0.2f;
*bbaa = CBoundingBoxAligned(position, position);
bbaa->Expand(radius);
bbaa->Expand(insets);
}
ShadowMap::ShadowMap()
{
m = new ShadowMapInternals;
m->Framebuffer = 0;
m->Width = 0;
m->Height = 0;
m->QualityLevel = 0;
m->EffectiveWidth = 0;
m->EffectiveHeight = 0;
m->DepthTextureBits = 0;
// DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
// but they're very much slower on Radeon 9800.
// In both cases, the default (no specified depth) is fast, so we just use
// that by default and hope it's alright. (Otherwise, we'd probably need to
// do some kind of hardware detection to work out what to use.)
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
m->LightTransform.SetIdentity();
m->UpdateCascadesParameters();
}
ShadowMap::~ShadowMap()
{
m->Framebuffer.reset();
m->Texture.reset();
m->DummyTexture.reset();
delete m;
}
// Force the texture/buffer/etc to be recreated, particularly when the renderer's
// size has changed
void ShadowMap::RecreateTexture()
{
m->Framebuffer.reset();
m->Texture.reset();
m->DummyTexture.reset();
m->UpdateCascadesParameters();
// (Texture will be constructed in next SetupFrame)
}
// SetupFrame: camera and light direction for this frame
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
{
if (!m->Texture)
m->CreateTexture();
CVector3D x(0, 1, 0), eyepos;
CVector3D z = lightdir;
z.Normalize();
x -= z * z.Dot(x);
if (x.Length() < 0.001)
{
// this is invoked if the camera and light directions almost coincide
// assumption: light direction has a significant Z component
x = CVector3D(1.0, 0.0, 0.0);
x -= z * z.Dot(x);
}
x.Normalize();
CVector3D y = z.Cross(x);
// X axis perpendicular to light direction, flowing along with view direction
m->LightTransform._11 = x.X;
m->LightTransform._12 = x.Y;
m->LightTransform._13 = x.Z;
// Y axis perpendicular to light and view direction
m->LightTransform._21 = y.X;
m->LightTransform._22 = y.Y;
m->LightTransform._23 = y.Z;
// Z axis is in direction of light
m->LightTransform._31 = z.X;
m->LightTransform._32 = z.Y;
m->LightTransform._33 = z.Z;
// eye is at the origin of the coordinate system
m->LightTransform._14 = -x.Dot(eyepos);
m->LightTransform._24 = -y.Dot(eyepos);
m->LightTransform._34 = -z.Dot(eyepos);
m->LightTransform._41 = 0.0;
m->LightTransform._42 = 0.0;
m->LightTransform._43 = 0.0;
m->LightTransform._44 = 1.0;
m->LightTransform.GetInverse(m->InvLightTransform);
m->ShadowReceiverBound.SetEmpty();
m->LightspaceCamera = camera;
m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
m->LightspaceCamera.UpdateFrustum();
m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE;
m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO;
CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance);
CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio);
m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f);
m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance;
for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade)
m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio;
if (GetCascadeCount() == 1 || m->ShadowsCoverMap)
{
m->Cascades[0].ViewPort =
SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2};
if (m->ShadowsCoverMap)
m->Cascades[0].Distance = camera.GetFarPlane();
}
else
{
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
{
const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0;
const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0;
m->Cascades[cascade].ViewPort =
SViewPort{offsetX + 1, offsetY + 1,
m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2};
}
}
for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx)
{
ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx];
const float nearPlane = cascadeIdx > 0 ?
m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane();
const float farPlane = cascade.Distance;
CalculateBoundsForCascade(camera, m->LightTransform,
nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA);
cascade.ShadowCasterBound.SetEmpty();
}
}
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
// objects
void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
}
void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->ShadowReceiverBound += lightspacebounds;
}
CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
{
// Get the bounds of all objects that can receive shadows
CBoundingBoxAligned bound = m->ShadowReceiverBound;
// Intersect with the camera frustum, so the shadow map doesn't have to get
// stretched to cover the off-screen parts of large models
bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
// ShadowBound might have been empty to begin with, producing an empty result
if (bound.IsEmpty())
{
// CFrustum can't easily represent nothingness, so approximate it with
// a single point which won't match many objects
bound += CVector3D(0.0f, 0.0f, 0.0f);
return bound.ToFrustum();
}
// Extend the bounds a long way towards the light source, to encompass
// all objects that might cast visible shadows.
// (The exact constant was picked entirely arbitrarily.)
bound[0].Z -= 1000.f;
CFrustum frustum = bound.ToFrustum();
frustum.Transform(m->InvLightTransform);
return frustum;
}
// CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
// projection and transformation matrices
void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
{
CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
shadowRenderBound = Cascades[cascade].ConvexBounds;
if (ShadowsCoverMap)
{
// Start building the shadow map to cover all objects that will receive shadows
CBoundingBoxAligned receiverBound = ShadowReceiverBound;
// Intersect with the camera frustum, so the shadow map doesn't have to get
// stretched to cover the off-screen parts of large models
receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
// Intersect with the shadow caster bounds, because there's no point
// wasting space around the edges of the shadow map that we're not going
// to draw into
shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X);
shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y);
shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X);
shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y);
}
else if (CascadeCount > 1)
{
// We need to offset the cascade to its place on the texture.
const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f;
if (!(cascade & 0x1))
shadowRenderBound[1].X += size.X * 2.0f;
else
shadowRenderBound[0].X -= size.X * 2.0f;
if (!(cascade & 0x2))
shadowRenderBound[1].Y += size.Y * 2.0f;
else
shadowRenderBound[0].Y -= size.Y * 2.0f;
}
// Set the near and far planes to include just the shadow casters,
// so we make full use of the depth texture's range. Add a bit of a
// delta so we don't accidentally clip objects that are directly on
// the planes.
shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f;
shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f;
// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
if (scale.X < 1.0)
scale.X = 1.0;
if (scale.Y < 1.0)
scale.Y = 1.0;
if (scale.Z < 1.0)
scale.Z = 1.0;
scale.X = 2.0 / scale.X;
scale.Y = 2.0 / scale.Y;
scale.Z = 2.0 / scale.Z;
// make sure a given world position falls on a consistent shadowmap texel fractional offset
float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
CMatrix3D& lightProjection = Cascades[cascade].LightProjection;
lightProjection.SetZero();
lightProjection._11 = scale.X;
lightProjection._14 = (shift.X + offsetX) * scale.X;
lightProjection._22 = scale.Y;
lightProjection._24 = (shift.Y + offsetY) * scale.Y;
lightProjection._33 = scale.Z;
lightProjection._34 = shift.Z * scale.Z;
lightProjection._44 = 1.0;
// Calculate texture matrix by creating the clip space to texture coordinate matrix
// and then concatenating all matrices that have been calculated so far
float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
float texscalez = scale.Z * 0.5f;
CMatrix3D lightToTex;
lightToTex.SetZero();
lightToTex._11 = texscalex;
lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
lightToTex._22 = texscaley;
lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
lightToTex._33 = texscalez;
lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
lightToTex._44 = 1.0;
Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
}
// Create the shadow map
void ShadowMapInternals::CreateTexture()
{
// Cleanup
Framebuffer.reset();
Texture.reset();
DummyTexture.reset();
Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
CFG_GET_VAL("shadowquality", QualityLevel);
// Get shadow map size as next power of two up from view width/height.
int shadowMapSize;
switch (QualityLevel)
{
// Low
case -1:
shadowMapSize = 512;
break;
// High
case 1:
shadowMapSize = 2048;
break;
// Ultra
case 2:
shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())), 4096);
break;
// Medium as is
default:
shadowMapSize = 1024;
break;
}
// Clamp to the maximum texture size.
shadowMapSize = std::min(
shadowMapSize, static_cast(backendDevice->GetCapabilities().maxTextureSize));
Width = Height = shadowMapSize;
// Since we're using a framebuffer object, the whole texture is available
EffectiveWidth = Width;
EffectiveHeight = Height;
const char* formatName;
Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED;
#if CONFIG2_GLES
formatName = "Format::D24";
backendFormat = Renderer::Backend::Format::D24;
#else
switch (DepthTextureBits)
{
case 16: formatName = "Format::D16"; backendFormat = Renderer::Backend::Format::D16; break;
case 24: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break;
case 32: formatName = "Format::D32"; backendFormat = Renderer::Backend::Format::D32; break;
default: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break;
}
#endif
ENSURE(formatName);
LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
Width, Height, formatName);
if (g_RenderingOptions.GetShadowAlphaFix())
{
DummyTexture = backendDevice->CreateTexture2D("ShadowMapDummy",
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
}
Renderer::Backend::Sampler::Desc samplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
#if CONFIG2_GLES
// GLES doesn't do depth comparisons, so treat it as a
// basic unfiltered depth texture
Renderer::Backend::Sampler::Filter::NEAREST,
#else
// Use LINEAR to trigger automatic PCF on some devices.
Renderer::Backend::Sampler::Filter::LINEAR,
#endif
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
// Enable automatic depth comparisons
samplerDesc.compareEnabled = true;
samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL;
Texture = backendDevice->CreateTexture2D("ShadowMapDepth",
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
backendFormat, Width, Height, samplerDesc);
const bool useDummyTexture = g_RenderingOptions.GetShadowAlphaFix();
// In case we used ShadowAlphaFix, we ought to clear the unused
// color buffer too, else Mali 400 drivers get confused.
// Might as well clear stencil too for completeness.
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = DummyTexture.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::DONT_CARE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
depthStencilAttachment.texture = Texture.get();
depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
Framebuffer = backendDevice->CreateFramebuffer("ShadowMapFramebuffer",
useDummyTexture ? &colorAttachment : nullptr, &depthStencilAttachment);
if (!Framebuffer)
{
LOGERROR("Failed to create shadows framebuffer");
// Disable shadow rendering (but let the user try again if they want).
g_RenderingOptions.SetShadows(false);
}
}
void ShadowMap::BeginRender(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
deviceCommandContext->SetGraphicsPipelineState(
Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
ENSURE(m->Framebuffer);
deviceCommandContext->BeginFramebufferPass(m->Framebuffer.get());
m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
}
void ShadowMap::PrepareCamera(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const int cascade)
{
m->CalculateShadowMatrices(cascade);
- const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
- g_Renderer.SetViewport(vp);
+ Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
+ viewportRect.width = m->EffectiveWidth;
+ viewportRect.height = m->EffectiveHeight;
+ deviceCommandContext->SetViewports(1, &viewportRect);
CCamera camera = m->SavedViewCamera;
camera.SetProjection(m->Cascades[cascade].LightProjection);
camera.GetOrientation() = m->InvLightTransform;
g_Renderer.GetSceneRenderer().SetViewCamera(camera);
const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
scissorRect.x = cascadeViewPort.m_X;
scissorRect.y = cascadeViewPort.m_Y;
scissorRect.width = cascadeViewPort.m_Width;
scissorRect.height = cascadeViewPort.m_Height;
deviceCommandContext->SetScissors(1, &scissorRect);
}
void ShadowMap::EndRender(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
deviceCommandContext->SetScissors(0, nullptr);
deviceCommandContext->EndFramebufferPass();
g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera);
-
- const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() };
- g_Renderer.SetViewport(vp);
}
void ShadowMap::BindTo(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
Renderer::Backend::IShaderProgram* shader) const
{
const int32_t shadowTexBindingSlot = shader->GetBindingSlot(str_shadowTex);
if (shadowTexBindingSlot < 0 || !m->Texture)
return;
deviceCommandContext->SetTexture(shadowTexBindingSlot, m->Texture.get());
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_shadowScale), m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
const CVector3D cameraForward = g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn();
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_cameraForward), cameraForward.X, cameraForward.Y, cameraForward.Z,
cameraForward.Dot(g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetTranslation()));
if (GetCascadeCount() == 1)
{
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_shadowTransform),
m->Cascades[0].TextureMatrix.AsFloatArray());
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_shadowDistance), m->Cascades[0].Distance);
}
else
{
std::vector shadowDistances;
std::vector shadowTransforms;
for (const ShadowMapInternals::Cascade& cascade : m->Cascades)
{
shadowDistances.emplace_back(cascade.Distance);
shadowTransforms.emplace_back(cascade.TextureMatrix);
}
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_shadowTransform),
PS::span(
shadowTransforms[0]._data,
shadowTransforms[0].AsFloatArray().size() * GetCascadeCount()));
deviceCommandContext->SetUniform(
shader->GetBindingSlot(str_shadowDistance),
PS::span(shadowDistances.data(), shadowDistances.size()));
}
}
// Depth texture bits
int ShadowMap::GetDepthTextureBits() const
{
return m->DepthTextureBits;
}
void ShadowMap::SetDepthTextureBits(int bits)
{
if (bits != m->DepthTextureBits)
{
m->Texture.reset();
m->Width = m->Height = 0;
m->DepthTextureBits = bits;
}
}
void ShadowMap::RenderDebugBounds()
{
// Render various shadow bounds:
// Yellow = bounds of objects in view frustum that receive shadows
// Red = culling frustum used to find potential shadow casters
// Blue = frustum used for rendering the shadow map
const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform;
g_Renderer.GetDebugRenderer().DrawBoundingBox(
m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true);
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
{
g_Renderer.GetDebugRenderer().DrawBoundingBox(
m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
g_Renderer.GetDebugRenderer().DrawBoundingBox(
m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true);
const CFrustum frustum = GetShadowCasterCullFrustum(cascade);
// We don't have a function to create a brush directly from a frustum, so use
// the ugly approach of creating a large cube and then intersecting with the frustum
const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
CBrush brush(dummy);
CBrush frustumBrush;
brush.Intersect(frustum, frustumBrush);
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true);
}
}
int ShadowMap::GetCascadeCount() const
{
#if CONFIG2_GLES
return 1;
#else
return m->ShadowsCoverMap ? 1 : m->CascadeCount;
#endif
}
Index: ps/trunk/source/renderer/backend/IDevice.h
===================================================================
--- ps/trunk/source/renderer/backend/IDevice.h (revision 27312)
+++ ps/trunk/source/renderer/backend/IDevice.h (revision 27313)
@@ -1,150 +1,157 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_IDEVICE
#define INCLUDED_RENDERER_BACKEND_IDEVICE
#include "graphics/Color.h"
#include "renderer/backend/Backend.h"
#include "renderer/backend/Format.h"
#include "renderer/backend/IBuffer.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/backend/IFramebuffer.h"
#include "renderer/backend/IShaderProgram.h"
#include "renderer/backend/ITexture.h"
#include "scriptinterface/ScriptForward.h"
#include
#include
#include
class CShaderDefines;
class CStr;
namespace Renderer
{
namespace Backend
{
class IDevice
{
public:
struct Capabilities
{
bool S3TC;
bool ARBShaders;
bool ARBShadersShadow;
bool computeShaders;
bool debugLabels;
bool debugScopedLabels;
bool multisampling;
bool anisotropicFiltering;
uint32_t maxSampleCount;
float maxAnisotropy;
uint32_t maxTextureSize;
bool instancing;
};
virtual ~IDevice() {}
virtual Backend GetBackend() const = 0;
virtual const std::string& GetName() const = 0;
virtual const std::string& GetVersion() const = 0;
virtual const std::string& GetDriverInformation() const = 0;
virtual const std::vector& GetExtensions() const = 0;
virtual void Report(const ScriptRequest& rq, JS::HandleValue settings) = 0;
virtual std::unique_ptr CreateCommandContext() = 0;
virtual std::unique_ptr CreateTexture(
const char* name, const ITexture::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) = 0;
virtual std::unique_ptr CreateTexture2D(
const char* name, const uint32_t usage,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) = 0;
/**
* @see IFramebuffer
*
* The color attachment and the depth-stencil attachment should not be
* nullptr at the same time. There should not be many different clear
* colors along all color attachments for all framebuffers created for
* the device.
*
* @return A valid framebuffer if it was created successfully else nullptr.
*/
virtual std::unique_ptr CreateFramebuffer(
const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment) = 0;
virtual std::unique_ptr CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) = 0;
virtual std::unique_ptr CreateShaderProgram(
const CStr& name, const CShaderDefines& defines) = 0;
/**
* Acquires a backbuffer for rendering a frame.
*
* @return True if it was successfully acquired and we can render to it.
*/
virtual bool AcquireNextBackbuffer() = 0;
/**
* Returns a framebuffer for the current backbuffer with the required
* attachment operations. It should not be called if the last
* AcquireNextBackbuffer call returned false.
*
* It's guaranteed that for the same acquired backbuffer this function returns
* a framebuffer with the same attachments and properties except load and
* store operations.
*
* @return The last successfully acquired framebuffer that wasn't
* presented.
*/
virtual IFramebuffer* GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp) = 0;
/**
* Presents the backbuffer to the swapchain queue to be flipped on a
* screen. Should be called only if the last AcquireNextBackbuffer call
* returned true.
*/
virtual void Present() = 0;
+ /**
+ * Should be called on window surface resize. It's the device owner
+ * responsibility to call that function. Shouldn't be called during
+ * rendering to an acquired backbuffer.
+ */
+ virtual void OnWindowResize(const uint32_t width, const uint32_t height) = 0;
+
virtual bool IsTextureFormatSupported(const Format format) const = 0;
virtual bool IsFramebufferFormatSupported(const Format format) const = 0;
virtual const Capabilities& GetCapabilities() const = 0;
};
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_IDEVICE
Index: ps/trunk/source/renderer/backend/IFramebuffer.h
===================================================================
--- ps/trunk/source/renderer/backend/IFramebuffer.h (revision 27312)
+++ ps/trunk/source/renderer/backend/IFramebuffer.h (revision 27313)
@@ -1,98 +1,101 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_IFRAMEBUFFER
#define INCLUDED_RENDERER_BACKEND_IFRAMEBUFFER
#include "graphics/Color.h"
#include "renderer/backend/IDeviceObject.h"
namespace Renderer
{
namespace Backend
{
class ITexture;
/**
* Load operation is set for each attachment, what should be done with its
* content on BeginFramebufferPass.
*/
enum class AttachmentLoadOp
{
// Loads the attachment content.
LOAD,
// Clears the attachment content without loading. Prefer to use that
// operation over manual ClearFramebuffer.
CLEAR,
// After BeginFramebufferPass the attachment content is undefined.
DONT_CARE
};
/**
* Store operation is set for each attachment, what should be done with its
* content on EndFramebufferPass.
*/
enum class AttachmentStoreOp
{
// Stores the attachment content.
STORE,
// After EndFramebufferPass the attachment content is undefined.
DONT_CARE
};
struct SColorAttachment
{
ITexture* texture = nullptr;
AttachmentLoadOp loadOp = AttachmentLoadOp::DONT_CARE;
AttachmentStoreOp storeOp = AttachmentStoreOp::DONT_CARE;
CColor clearColor;
};
struct SDepthStencilAttachment
{
ITexture* texture = nullptr;
AttachmentLoadOp loadOp = AttachmentLoadOp::DONT_CARE;
AttachmentStoreOp storeOp = AttachmentStoreOp::DONT_CARE;
};
/**
* IFramebuffer stores attachments which should be used by backend as rendering
* destinations. That combining allows to set these destinations at once.
* IFramebuffer doesn't own its attachments so clients must keep them alive.
* The number of framebuffers ever created for a device during its lifetime
* should be as small as possible.
*
* Framebuffer is a term from OpenGL/Vulkan worlds (D3D synonym is a render
* target).
*/
class IFramebuffer : public IDeviceObject
{
public:
/**
* Returns a clear color for all color attachments of the framebuffer.
* @see IDevice::CreateFramebuffer()
*/
virtual const CColor& GetClearColor() const = 0;
+
+ virtual uint32_t GetWidth() const = 0;
+ virtual uint32_t GetHeight() const = 0;
};
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_IFRAMEBUFFER
Index: ps/trunk/source/renderer/backend/dummy/Device.cpp
===================================================================
--- ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 27312)
+++ ps/trunk/source/renderer/backend/dummy/Device.cpp (revision 27313)
@@ -1,148 +1,152 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Device.h"
#include "renderer/backend/dummy/Buffer.h"
#include "renderer/backend/dummy/DeviceCommandContext.h"
#include "renderer/backend/dummy/Framebuffer.h"
#include "renderer/backend/dummy/ShaderProgram.h"
#include "renderer/backend/dummy/Texture.h"
#include "scriptinterface/JSON.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptRequest.h"
namespace Renderer
{
namespace Backend
{
namespace Dummy
{
CDevice::CDevice()
{
m_Name = "Dummy";
m_Version = "Unknown";
m_DriverInformation = "Unknown";
m_Extensions = {};
m_Backbuffer = CFramebuffer::Create(this);
m_Capabilities.S3TC = true;
m_Capabilities.ARBShaders = false;
m_Capabilities.ARBShadersShadow = false;
m_Capabilities.computeShaders = true;
m_Capabilities.debugLabels = true;
m_Capabilities.debugScopedLabels = true;
m_Capabilities.multisampling = true;
m_Capabilities.anisotropicFiltering = true;
m_Capabilities.maxSampleCount = 4u;
m_Capabilities.maxAnisotropy = 16.0f;
m_Capabilities.maxTextureSize = 8192u;
m_Capabilities.instancing = true;
}
CDevice::~CDevice() = default;
void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
{
Script::SetProperty(rq, settings, "name", "dummy");
}
std::unique_ptr CDevice::CreateCommandContext()
{
return CDeviceCommandContext::Create(this);
}
std::unique_ptr CDevice::CreateTexture(
const char* UNUSED(name), const CTexture::Type type, const uint32_t usage,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& UNUSED(defaultSamplerDesc), const uint32_t MIPLevelCount, const uint32_t UNUSED(sampleCount))
{
return CTexture::Create(this, type, usage, format, width, height, MIPLevelCount);
}
std::unique_ptr CDevice::CreateTexture2D(
const char* name, 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)
{
return CreateTexture(name, ITexture::Type::TEXTURE_2D, usage,
format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount);
}
std::unique_ptr CDevice::CreateFramebuffer(
const char*, SColorAttachment*, SDepthStencilAttachment*)
{
return CFramebuffer::Create(this);
}
std::unique_ptr CDevice::CreateBuffer(
const char*, const CBuffer::Type type, const uint32_t size, const bool dynamic)
{
return CBuffer::Create(this, type, size, dynamic);
}
std::unique_ptr CDevice::CreateShaderProgram(
const CStr&, const CShaderDefines&)
{
return CShaderProgram::Create(this);
}
bool CDevice::AcquireNextBackbuffer()
{
// We have nothing to acquire.
return true;
}
IFramebuffer* CDevice::GetCurrentBackbuffer(
const AttachmentLoadOp, const AttachmentStoreOp,
const AttachmentLoadOp, const AttachmentStoreOp)
{
return m_Backbuffer.get();
}
void CDevice::Present()
{
// We have nothing to present.
}
+void CDevice::OnWindowResize(const uint32_t UNUSED(width), const uint32_t UNUSED(height))
+{
+}
+
bool CDevice::IsTextureFormatSupported(const Format UNUSED(format)) const
{
return true;
}
bool CDevice::IsFramebufferFormatSupported(const Format UNUSED(format)) const
{
return true;
}
std::unique_ptr CreateDevice(SDL_Window* UNUSED(window))
{
return std::make_unique();
}
} // namespace Dummy
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/dummy/Device.h
===================================================================
--- ps/trunk/source/renderer/backend/dummy/Device.h (revision 27312)
+++ ps/trunk/source/renderer/backend/dummy/Device.h (revision 27313)
@@ -1,106 +1,108 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE
#define INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE
#include "renderer/backend/dummy/DeviceForward.h"
#include "renderer/backend/IDevice.h"
class CShaderDefines;
namespace Renderer
{
namespace Backend
{
namespace Dummy
{
class CDeviceCommandContext;
class CDevice : public IDevice
{
public:
CDevice();
~CDevice() override;
Backend GetBackend() const override { return Backend::DUMMY; }
const std::string& GetName() const override { return m_Name; }
const std::string& GetVersion() const override { return m_Version; }
const std::string& GetDriverInformation() const override { return m_DriverInformation; }
const std::vector& GetExtensions() const override { return m_Extensions; }
void Report(const ScriptRequest& rq, JS::HandleValue settings) override;
std::unique_ptr CreateCommandContext() override;
std::unique_ptr CreateTexture(
const char* name, const ITexture::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) override;
std::unique_ptr CreateTexture2D(
const char* name, const uint32_t usage,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override;
std::unique_ptr CreateFramebuffer(
const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment) override;
std::unique_ptr CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override;
std::unique_ptr CreateShaderProgram(
const CStr& name, const CShaderDefines& defines) override;
bool AcquireNextBackbuffer() override;
IFramebuffer* GetCurrentBackbuffer(
const AttachmentLoadOp, const AttachmentStoreOp,
const AttachmentLoadOp, const AttachmentStoreOp) override;
void Present() override;
+ void OnWindowResize(const uint32_t width, const uint32_t height) override;
+
bool IsTextureFormatSupported(const Format format) const override;
bool IsFramebufferFormatSupported(const Format format) const override;
const Capabilities& GetCapabilities() const override { return m_Capabilities; }
protected:
std::string m_Name;
std::string m_Version;
std::string m_DriverInformation;
std::vector m_Extensions;
std::unique_ptr m_Backbuffer;
Capabilities m_Capabilities{};
};
} // namespace Dummy
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_DUMMY_DEVICE
Index: ps/trunk/source/renderer/backend/dummy/Framebuffer.h
===================================================================
--- ps/trunk/source/renderer/backend/dummy/Framebuffer.h (revision 27312)
+++ ps/trunk/source/renderer/backend/dummy/Framebuffer.h (revision 27313)
@@ -1,63 +1,66 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_DUMMY_FRAMEBUFFER
#define INCLUDED_RENDERER_BACKEND_DUMMY_FRAMEBUFFER
#include "renderer/backend/IFramebuffer.h"
#include
namespace Renderer
{
namespace Backend
{
namespace Dummy
{
class CDevice;
class CFramebuffer : public IFramebuffer
{
public:
~CFramebuffer() override;
IDevice* GetDevice() override;
const CColor& GetClearColor() const override { return m_ClearColor; }
+ uint32_t GetWidth() const override { return 1; }
+ uint32_t GetHeight() const override { return 1; }
+
private:
friend class CDevice;
static std::unique_ptr Create(CDevice* device);
CFramebuffer();
CDevice* m_Device = nullptr;
CColor m_ClearColor;
};
} // namespace Dummy
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_DUMMY_FRAMEBUFFER
Index: ps/trunk/source/renderer/backend/gl/Device.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 27312)
+++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 27313)
@@ -1,1043 +1,1054 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Device.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/hash.h"
#include "lib/ogl.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Profile.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Texture.h"
#include "scriptinterface/JSON.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptRequest.h"
#if OS_WIN
#include "lib/sysdep/os/win/wgfx.h"
// We can't include wutil directly because GL headers conflict with Windows
// until we use a proper GL loader.
extern void* wutil_GetAppHDC();
#endif
#include
#include
#include
#if !CONFIG2_GLES && (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND))
#if defined(SDL_VIDEO_DRIVER_X11)
#include
#endif
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
#include
#endif
#include
#endif // !CONFIG2_GLES && (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND))
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
std::string GetNameImpl()
{
// GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards.
// On top of that WMI can cause crashes with Nvidia Optimus and some netbooks
// see http://trac.wildfiregames.com/ticket/1952
// http://trac.wildfiregames.com/ticket/1575
char cardName[128];
const char* vendor = reinterpret_cast(glGetString(GL_VENDOR));
const char* renderer = reinterpret_cast(glGetString(GL_RENDERER));
// Happens if called before GL initialization.
if (!vendor || !renderer)
return {};
sprintf_s(cardName, std::size(cardName), "%s %s", vendor, renderer);
// Remove crap from vendor names. (don't dare touch the model name -
// it's too risky, there are too many different strings).
#define SHORTEN(what, charsToKeep) \
if (!strncmp(cardName, what, std::size(what) - 1)) \
memmove(cardName + charsToKeep, cardName + std::size(what) - 1, (strlen(cardName) - (std::size(what) - 1) + 1) * sizeof(char));
SHORTEN("ATI Technologies Inc.", 3);
SHORTEN("NVIDIA Corporation", 6);
SHORTEN("S3 Graphics", 2); // returned by EnumDisplayDevices
SHORTEN("S3 Graphics, Incorporated", 2); // returned by GL_VENDOR
#undef SHORTEN
return cardName;
}
std::string GetVersionImpl()
{
return reinterpret_cast(glGetString(GL_VERSION));
}
std::string GetDriverInformationImpl()
{
const std::string version = GetVersionImpl();
std::string driverInfo;
#if OS_WIN
driverInfo = CStrW(wgfx_DriverInfo()).ToUTF8();
if (driverInfo.empty())
#endif
{
if (!version.empty())
{
// Add "OpenGL" to differentiate this from the real driver version
// (returned by platform-specific detect routines).
driverInfo = std::string("OpenGL ") + version;
}
}
if (driverInfo.empty())
return version;
return version + " " + driverInfo;
}
std::vector GetExtensionsImpl()
{
std::vector extensions;
const std::string exts = ogl_ExtensionString();
boost::split(extensions, exts, boost::algorithm::is_space(), boost::token_compress_on);
std::sort(extensions.begin(), extensions.end());
return extensions;
}
void GLAD_API_PTR OnDebugMessage(
GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei UNUSED(length), const GLchar* message, const void* UNUSED(user_param))
{
std::string debugSource = "unknown";
std::string debugType = "unknown";
std::string debugSeverity = "unknown";
switch (source)
{
case GL_DEBUG_SOURCE_API:
debugSource = "the API";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
debugSource = "the window system";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
debugSource = "the shader compiler";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
debugSource = "a third party";
break;
case GL_DEBUG_SOURCE_APPLICATION:
debugSource = "the application";
break;
case GL_DEBUG_SOURCE_OTHER:
debugSource = "somewhere";
break;
}
switch (type)
{
case GL_DEBUG_TYPE_ERROR:
debugType = "error";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
debugType = "deprecated behaviour";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
debugType = "undefined behaviour";
break;
case GL_DEBUG_TYPE_PORTABILITY:
debugType = "portability";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
debugType = "performance";
break;
case GL_DEBUG_TYPE_OTHER:
debugType = "other";
break;
case GL_DEBUG_TYPE_MARKER:
debugType = "marker";
break;
case GL_DEBUG_TYPE_PUSH_GROUP:
debugType = "push group";
break;
case GL_DEBUG_TYPE_POP_GROUP:
debugType = "pop group";
break;
}
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH:
debugSeverity = "high";
break;
case GL_DEBUG_SEVERITY_MEDIUM:
debugSeverity = "medium";
break;
case GL_DEBUG_SEVERITY_LOW:
debugSeverity = "low";
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
debugSeverity = "notification";
break;
}
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
{
debug_printf(
"OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message);
}
else
{
LOGWARNING(
"OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message);
}
}
} // anonymous namespace
// static
std::unique_ptr CDevice::Create(SDL_Window* window, const bool arb)
{
std::unique_ptr device(new CDevice());
if (window)
{
// According to https://wiki.libsdl.org/SDL_CreateWindow we don't need to
// call SDL_GL_LoadLibrary if we have a window with SDL_WINDOW_OPENGL,
// because it'll be called internally for the first created window.
device->m_Window = window;
device->m_Context = SDL_GL_CreateContext(device->m_Window);
if (!device->m_Context)
{
LOGERROR("SDL_GL_CreateContext failed: '%s'", SDL_GetError());
return nullptr;
}
+ SDL_GL_GetDrawableSize(window, &device->m_SurfaceDrawableWidth, &device->m_SurfaceDrawableHeight);
+
#if OS_WIN
ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC());
#elif (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)) && !CONFIG2_GLES
SDL_SysWMinfo wminfo;
// The info structure must be initialized with the SDL version.
SDL_VERSION(&wminfo.version);
if (!SDL_GetWindowWMInfo(window, &wminfo))
{
LOGERROR("Failed to query SDL WM info: %s", SDL_GetError());
return nullptr;
}
switch (wminfo.subsystem)
{
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
case SDL_SYSWM_WAYLAND:
// TODO: maybe we need to load X11 functions
// dynamically as well.
ogl_Init(SDL_GL_GetProcAddress,
GetWaylandDisplay(device->m_Window),
static_cast(wminfo.subsystem));
break;
#endif
#if defined(SDL_VIDEO_DRIVER_X11)
case SDL_SYSWM_X11:
ogl_Init(SDL_GL_GetProcAddress,
GetX11Display(device->m_Window),
static_cast(wminfo.subsystem));
break;
#endif
default:
ogl_Init(SDL_GL_GetProcAddress, nullptr,
static_cast(wminfo.subsystem));
break;
}
#else
ogl_Init(SDL_GL_GetProcAddress);
#endif
}
else
{
#if OS_WIN
ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC());
#elif (defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)) && !CONFIG2_GLES
bool initialized = false;
// Currently we don't have access to the backend type without
// the window. So we use hack to detect X11.
#if defined(SDL_VIDEO_DRIVER_X11)
Display* display = XOpenDisplay(NULL);
if (display)
{
ogl_Init(SDL_GL_GetProcAddress, display, static_cast(SDL_SYSWM_X11));
initialized = true;
}
#endif
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
if (!initialized)
{
// glad will find default EGLDisplay internally.
ogl_Init(SDL_GL_GetProcAddress, nullptr, static_cast(SDL_SYSWM_WAYLAND));
initialized = true;
}
#endif
if (!initialized)
{
LOGERROR("Can't initialize GL");
return nullptr;
}
#else
ogl_Init(SDL_GL_GetProcAddress);
#endif
#if OS_WIN || defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
// Hack to stop things looking very ugly when scrolling in Atlas.
ogl_SetVsyncEnabled(true);
#endif
}
// If we don't have GL2.0 then we don't have GLSL in core.
if (!arb && !ogl_HaveVersion(2, 0))
return nullptr;
if ((ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr) // ARB
&& !ogl_HaveVersion(2, 0)) // GLSL
|| !ogl_HaveExtension("GL_ARB_vertex_buffer_object") // VBO
|| ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", nullptr)
|| (!ogl_HaveExtension("GL_EXT_framebuffer_object") && !ogl_HaveExtension("GL_ARB_framebuffer_object")))
{
// It doesn't make sense to continue working here, because we're not
// able to display anything.
DEBUG_DISPLAY_FATAL_ERROR(
L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
L" The game does not support pre-shader graphics cards."
L" You are advised to try installing newer drivers and/or upgrade your graphics card."
L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
);
}
device->m_ARB = arb;
device->m_Name = GetNameImpl();
device->m_Version = GetVersionImpl();
device->m_DriverInformation = GetDriverInformationImpl();
device->m_Extensions = GetExtensionsImpl();
// Set packing parameters for uploading and downloading data.
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glEnable(GL_TEXTURE_2D);
if (arb)
{
#if !CONFIG2_GLES
glEnable(GL_VERTEX_PROGRAM_ARB);
glEnable(GL_FRAGMENT_PROGRAM_ARB);
#endif
}
#if CONFIG2_GLES
device->m_UseFramebufferInvalidating = ogl_HaveExtension("GL_EXT_discard_framebuffer");
#else
device->m_UseFramebufferInvalidating = !arb && ogl_HaveExtension("GL_ARB_invalidate_subdata");
#endif
Capabilities& capabilities = device->m_Capabilities;
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");
#if CONFIG2_GLES
// Some GLES implementations have GL_EXT_texture_compression_dxt1
// but that only supports DXT1 so we can't use it.
capabilities.S3TC = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", nullptr) == 0;
#else
// Note: we don't bother checking for GL_S3_s3tc - it is incompatible
// and irrelevant (was never widespread).
capabilities.S3TC = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", nullptr) == 0;
#endif
#if CONFIG2_GLES
capabilities.multisampling = false;
capabilities.maxSampleCount = 1;
#else
capabilities.multisampling =
ogl_HaveVersion(3, 3) &&
ogl_HaveExtension("GL_ARB_multisample") &&
ogl_HaveExtension("GL_ARB_texture_multisample");
if (capabilities.multisampling)
{
// By default GL_MULTISAMPLE should be enabled, but enable it for buggy drivers.
glEnable(GL_MULTISAMPLE);
GLint maxSamples = 1;
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
capabilities.maxSampleCount = maxSamples;
}
#endif
capabilities.anisotropicFiltering = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic");
if (capabilities.anisotropicFiltering)
{
GLfloat maxAnisotropy = 1.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
capabilities.maxAnisotropy = maxAnisotropy;
}
GLint maxTextureSize = 1024;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
capabilities.maxTextureSize = maxTextureSize;
#if CONFIG2_GLES
const bool isDebugInCore = ogl_HaveVersion(3, 2);
#else
const bool isDebugInCore = ogl_HaveVersion(4, 3);
#endif
const bool hasDebug = isDebugInCore || ogl_HaveExtension("GL_KHR_debug");
if (hasDebug)
{
#ifdef NDEBUG
bool enableDebugMessages = false;
CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages);
capabilities.debugLabels = false;
CFG_GET_VAL("renderer.backend.debuglabels", capabilities.debugLabels);
capabilities.debugScopedLabels = false;
CFG_GET_VAL("renderer.backend.debugscopedlabels", capabilities.debugScopedLabels);
#else
const bool enableDebugMessages = true;
capabilities.debugLabels = true;
capabilities.debugScopedLabels = true;
#endif
if (enableDebugMessages)
{
glEnable(GL_DEBUG_OUTPUT);
#if !CONFIG2_GLES
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#else
#warning GLES without GL_DEBUG_OUTPUT_SYNCHRONOUS might call the callback from different threads which might be unsafe.
#endif
glDebugMessageCallback(OnDebugMessage, nullptr);
// Filter out our own debug group messages
const GLuint id = 0x0AD;
glDebugMessageControl(
GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE);
glDebugMessageControl(
GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE);
}
}
#if CONFIG2_GLES
capabilities.instancing = false;
#else
capabilities.instancing =
!device->m_ARB &&
(ogl_HaveVersion(3, 3) ||
(ogl_HaveExtension("GL_ARB_draw_instanced") &&
ogl_HaveExtension("GL_ARB_instanced_arrays")));
#endif
return device;
}
CDevice::CDevice() = default;
CDevice::~CDevice()
{
if (m_Context)
SDL_GL_DeleteContext(m_Context);
}
void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
{
const char* errstr = "(error)";
Script::SetProperty(rq, settings, "name", m_ARB ? "glarb" : "gl");
#define INTEGER(id) do { \
GLint i = -1; \
glGetIntegerv(GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #id, i); \
} while (false)
#define INTEGER2(id) do { \
GLint i[2] = { -1, -1 }; \
glGetIntegerv(GL_##id, i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \
} else { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", i[0]); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", i[1]); \
} \
} while (false)
#define FLOAT(id) do { \
GLfloat f = std::numeric_limits::quiet_NaN(); \
glGetFloatv(GL_##id, &f); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #id, f); \
} while (false)
#define FLOAT2(id) do { \
GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \
glGetFloatv(GL_##id, f); \
if (ogl_SquelchError(GL_INVALID_ENUM)) { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \
} else { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", f[0]); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", f[1]); \
} \
} while (false)
#define STRING(id) do { \
const char* c = (const char*)glGetString(GL_##id); \
if (!c) c = ""; \
if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \
Script::SetProperty(rq, settings, "GL_" #id, std::string(c)); \
} while (false)
#define QUERY(target, pname) do { \
GLint i = -1; \
glGetQueryivARB(GL_##target, GL_##pname, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, i); \
} while (false)
#define VERTEXPROGRAM(id) do { \
GLint i = -1; \
glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \
} while (false)
#define FRAGMENTPROGRAM(id) do { \
GLint i = -1; \
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \
} while (false)
#define BOOL(id) INTEGER(id)
ogl_WarnIfError();
// Core OpenGL 1.3:
// (We don't bother checking extension strings for anything older than 1.3;
// it'll just produce harmless warnings)
STRING(VERSION);
STRING(VENDOR);
STRING(RENDERER);
STRING(EXTENSIONS);
#if !CONFIG2_GLES
INTEGER(MAX_CLIP_PLANES);
#endif
INTEGER(SUBPIXEL_BITS);
#if !CONFIG2_GLES
INTEGER(MAX_3D_TEXTURE_SIZE);
#endif
INTEGER(MAX_TEXTURE_SIZE);
INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
INTEGER2(MAX_VIEWPORT_DIMS);
#if !CONFIG2_GLES
BOOL(RGBA_MODE);
BOOL(INDEX_MODE);
BOOL(DOUBLEBUFFER);
BOOL(STEREO);
#endif
FLOAT2(ALIASED_POINT_SIZE_RANGE);
FLOAT2(ALIASED_LINE_WIDTH_RANGE);
#if !CONFIG2_GLES
INTEGER(MAX_ELEMENTS_INDICES);
INTEGER(MAX_ELEMENTS_VERTICES);
INTEGER(MAX_TEXTURE_UNITS);
#endif
INTEGER(SAMPLE_BUFFERS);
INTEGER(SAMPLES);
// TODO: compressed texture formats
INTEGER(RED_BITS);
INTEGER(GREEN_BITS);
INTEGER(BLUE_BITS);
INTEGER(ALPHA_BITS);
#if !CONFIG2_GLES
INTEGER(INDEX_BITS);
#endif
INTEGER(DEPTH_BITS);
INTEGER(STENCIL_BITS);
#if !CONFIG2_GLES
// Core OpenGL 2.0 (treated as extensions):
if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
{
FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
}
if (ogl_HaveExtension("GL_ARB_occlusion_query"))
{
QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_ARB_shading_language_100"))
{
STRING(SHADING_LANGUAGE_VERSION_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_shader"))
{
INTEGER(MAX_VERTEX_ATTRIBS_ARB);
INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
INTEGER(MAX_VARYING_FLOATS_ARB);
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
}
if (ogl_HaveExtension("GL_ARB_fragment_shader"))
{
INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") ||
ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
{
INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_TEXTURE_COORDS_ARB);
}
if (ogl_HaveExtension("GL_ARB_draw_buffers"))
{
INTEGER(MAX_DRAW_BUFFERS_ARB);
}
// Core OpenGL 3.0:
if (ogl_HaveExtension("GL_EXT_gpu_shader4"))
{
INTEGER(MIN_PROGRAM_TEXEL_OFFSET_EXT); // no _EXT version of these in glext.h
INTEGER(MAX_PROGRAM_TEXEL_OFFSET_EXT);
}
if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
{
INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
}
if (ogl_HaveExtension("GL_EXT_framebuffer_multisample"))
{
INTEGER(MAX_SAMPLES_EXT);
}
if (ogl_HaveExtension("GL_EXT_texture_array"))
{
INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT);
}
if (ogl_HaveExtension("GL_EXT_transform_feedback"))
{
INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT);
INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT);
INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT);
}
// Other interesting extensions:
if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query"))
{
QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_ARB_timer_query"))
{
QUERY(TIMESTAMP, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
{
FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
}
if (ogl_HaveExtension("GL_ARB_texture_rectangle"))
{
INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB);
}
if (m_ARB)
{
if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
{
INTEGER(MAX_PROGRAM_MATRICES_ARB);
INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_program"))
{
VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
if (ogl_HaveExtension("GL_ARB_fragment_program"))
{
// The spec seems to say these should be supported, but
// Mesa complains about them so let's not bother
/*
VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
*/
}
}
if (ogl_HaveExtension("GL_ARB_fragment_program"))
{
FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
if (ogl_HaveExtension("GL_ARB_vertex_program"))
{
// The spec seems to say these should be supported, but
// Intel drivers on Windows complain about them so let's not bother
/*
FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
*/
}
}
}
if (ogl_HaveExtension("GL_ARB_geometry_shader4"))
{
INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB);
INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB);
INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB);
INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB);
INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
}
#else // CONFIG2_GLES
// Core OpenGL ES 2.0:
STRING(SHADING_LANGUAGE_VERSION);
INTEGER(MAX_VERTEX_ATTRIBS);
INTEGER(MAX_VERTEX_UNIFORM_VECTORS);
INTEGER(MAX_VARYING_VECTORS);
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS);
INTEGER(MAX_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_RENDERBUFFER_SIZE);
#endif // CONFIG2_GLES
// TODO: Support OpenGL platforms which don't use GLX as well.
#if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
#define GLXQCR_INTEGER(id) do { \
unsigned int i = UINT_MAX; \
if (glXQueryCurrentRendererIntegerMESA(id, &i)) \
Script::SetProperty(rq, settings, #id, i); \
} while (false)
#define GLXQCR_INTEGER2(id) do { \
unsigned int i[2] = { UINT_MAX, UINT_MAX }; \
if (glXQueryCurrentRendererIntegerMESA(id, i)) { \
Script::SetProperty(rq, settings, #id "[0]", i[0]); \
Script::SetProperty(rq, settings, #id "[1]", i[1]); \
} \
} while (false)
#define GLXQCR_INTEGER3(id) do { \
unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \
if (glXQueryCurrentRendererIntegerMESA(id, i)) { \
Script::SetProperty(rq, settings, #id "[0]", i[0]); \
Script::SetProperty(rq, settings, #id "[1]", i[1]); \
Script::SetProperty(rq, settings, #id "[2]", i[2]); \
} \
} while (false)
#define GLXQCR_STRING(id) do { \
const char* str = glXQueryCurrentRendererStringMESA(id); \
if (str) \
Script::SetProperty(rq, settings, #id ".string", str); \
} while (false)
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
const int ret = SDL_GetWindowWMInfo(m_Window, &wminfo);
if (ret && wminfo.subsystem == SDL_SYSWM_X11)
{
Display* dpy = wminfo.info.x11.display;
int scrnum = DefaultScreen(dpy);
const char* glxexts = glXQueryExtensionsString(dpy, scrnum);
Script::SetProperty(rq, settings, "glx_extensions", glxexts);
if (strstr(glxexts, "GLX_MESA_query_renderer") && glXQueryCurrentRendererIntegerMESA && glXQueryCurrentRendererStringMESA)
{
GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA);
GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA);
GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA);
GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA);
GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA);
GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA);
GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA);
GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA);
GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA);
}
}
#endif // SDL_VIDEO_DRIVER_X11
}
std::unique_ptr CDevice::CreateCommandContext()
{
std::unique_ptr commandContet = CDeviceCommandContext::Create(this);
m_ActiveCommandContext = commandContet.get();
return commandContet;
}
std::unique_ptr CDevice::CreateTexture(
const char* name, const ITexture::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)
{
return CTexture::Create(this, name, type, usage,
format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount);
}
std::unique_ptr CDevice::CreateTexture2D(
const char* name, 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)
{
return CreateTexture(name, CTexture::Type::TEXTURE_2D, usage,
format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount);
}
std::unique_ptr CDevice::CreateFramebuffer(
const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment)
{
return CFramebuffer::Create(
this, name, colorAttachment, depthStencilAttachment);
}
std::unique_ptr CDevice::CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic)
{
return CBuffer::Create(this, name, type, size, dynamic);
}
std::unique_ptr CDevice::CreateShaderProgram(
const CStr& name, const CShaderDefines& defines)
{
return CShaderProgram::Create(this, name, defines);
}
bool CDevice::AcquireNextBackbuffer()
{
ENSURE(!m_BackbufferAcquired);
m_BackbufferAcquired = true;
return true;
}
size_t CDevice::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;
}
IFramebuffer* CDevice::GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp)
{
const BackbufferKey key{
colorAttachmentLoadOp, colorAttachmentStoreOp,
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp};
auto it = m_Backbuffers.find(key);
if (it == m_Backbuffers.end())
{
it = m_Backbuffers.emplace(key, CFramebuffer::CreateBackbuffer(
- this, colorAttachmentLoadOp, colorAttachmentStoreOp,
+ this, m_SurfaceDrawableWidth, m_SurfaceDrawableHeight,
+ colorAttachmentLoadOp, colorAttachmentStoreOp,
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp)).first;
}
return it->second.get();
}
void CDevice::Present()
{
ENSURE(m_BackbufferAcquired);
m_BackbufferAcquired = false;
if (m_Window)
{
PROFILE3("swap buffers");
SDL_GL_SwapWindow(m_Window);
ogl_WarnIfError();
}
bool checkGLErrorAfterSwap = false;
CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap);
#if defined(NDEBUG)
if (!checkGLErrorAfterSwap)
return;
#endif
PROFILE3("error check");
// We have to check GL errors after SwapBuffer to avoid possible
// synchronizations during rendering.
if (GLenum err = glGetError())
ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err));
}
+void CDevice::OnWindowResize(const uint32_t width, const uint32_t height)
+{
+ ENSURE(!m_BackbufferAcquired);
+ m_Backbuffers.clear();
+ m_SurfaceDrawableWidth = width;
+ m_SurfaceDrawableHeight = height;
+}
+
bool CDevice::IsTextureFormatSupported(const Format format) const
{
bool supported = false;
switch (format)
{
case Format::UNDEFINED:
break;
case Format::R8G8B8_UNORM: FALLTHROUGH;
case Format::R8G8B8A8_UNORM: FALLTHROUGH;
case Format::A8_UNORM: FALLTHROUGH;
case Format::L8_UNORM:
supported = true;
break;
case Format::R32_SFLOAT: FALLTHROUGH;
case Format::R32G32_SFLOAT: FALLTHROUGH;
case Format::R32G32B32_SFLOAT: FALLTHROUGH;
case Format::R32G32B32A32_SFLOAT:
break;
case Format::D16: FALLTHROUGH;
case Format::D24: FALLTHROUGH;
case Format::D32:
supported = true;
break;
case Format::D24_S8:
#if !CONFIG2_GLES
supported = true;
#endif
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 supported = false;
switch (format)
{
case Format::UNDEFINED:
break;
#if !CONFIG2_GLES
case Format::R8_UNORM:
supported = ogl_HaveVersion(3, 0);
break;
#endif
case Format::R8G8B8A8_UNORM:
supported = true;
break;
default:
break;
}
return supported;
}
std::unique_ptr CreateDevice(SDL_Window* window, const bool arb)
{
return GL::CDevice::Create(window, arb);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Device.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/Device.h (revision 27312)
+++ ps/trunk/source/renderer/backend/gl/Device.h (revision 27313)
@@ -1,153 +1,156 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_GL_DEVICE
#define INCLUDED_RENDERER_BACKEND_GL_DEVICE
#include "renderer/backend/Format.h"
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/gl/DeviceForward.h"
#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/ShaderProgram.h"
#include "renderer/backend/gl/Texture.h"
#include "renderer/backend/IDevice.h"
#include "scriptinterface/ScriptForward.h"
#include
#include
#include
#include
#include
typedef struct SDL_Window SDL_Window;
typedef void* SDL_GLContext;
namespace Renderer
{
namespace Backend
{
namespace GL
{
class CDeviceCommandContext;
class CDevice final : public IDevice
{
public:
~CDevice() override;
/**
* Creates the GL device and the GL context for the window if it presents.
*/
static std::unique_ptr Create(SDL_Window* window, const bool arb);
Backend GetBackend() const override { return m_ARB ? Backend::GL_ARB : Backend::GL; }
const std::string& GetName() const override { return m_Name; }
const std::string& GetVersion() const override { return m_Version; }
const std::string& GetDriverInformation() const override { return m_DriverInformation; }
const std::vector& GetExtensions() const override { return m_Extensions; }
void Report(const ScriptRequest& rq, JS::HandleValue settings) override;
std::unique_ptr CreateCommandContext() override;
CDeviceCommandContext* GetActiveCommandContext() { return m_ActiveCommandContext; }
std::unique_ptr CreateTexture(
const char* name, const ITexture::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) override;
std::unique_ptr CreateTexture2D(
const char* name, const uint32_t usage,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override;
std::unique_ptr CreateFramebuffer(
const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment) override;
std::unique_ptr CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override;
std::unique_ptr CreateShaderProgram(
const CStr& name, const CShaderDefines& defines) override;
bool AcquireNextBackbuffer() override;
IFramebuffer* GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
void Present() override;
+ void OnWindowResize(const uint32_t width, const uint32_t height) override;
+
bool UseFramebufferInvalidating() const { return m_UseFramebufferInvalidating; }
bool IsTextureFormatSupported(const Format format) const override;
bool IsFramebufferFormatSupported(const Format format) const override;
const Capabilities& GetCapabilities() const override { return m_Capabilities; }
private:
CDevice();
SDL_Window* m_Window = nullptr;
SDL_GLContext m_Context = nullptr;
+ int m_SurfaceDrawableWidth = 0, m_SurfaceDrawableHeight = 0;
bool m_ARB = false;
std::string m_Name;
std::string m_Version;
std::string m_DriverInformation;
std::vector m_Extensions;
// GL can have the only one command context at once.
// TODO: remove as soon as we have no GL code outside backend, currently
// it's used only as a helper for transition.
CDeviceCommandContext* m_ActiveCommandContext = nullptr;
using BackbufferKey = std::tuple<
AttachmentLoadOp, AttachmentStoreOp,
AttachmentLoadOp, AttachmentStoreOp>;
struct BackbufferKeyHash
{
size_t operator()(const BackbufferKey& key) const;
};
// We use std::unordered_map to avoid storing sizes of Attachment*Op
// enumerations. If it becomes a performance issue we'll replace it
// by an array.
std::unordered_map<
BackbufferKey, std::unique_ptr, BackbufferKeyHash> m_Backbuffers;
bool m_BackbufferAcquired = false;
bool m_UseFramebufferInvalidating = false;
Capabilities m_Capabilities{};
};
} // namespace GL
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_GL_DEVICE
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 27312)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 27313)
@@ -1,1307 +1,1308 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "DeviceCommandContext.h"
#include "ps/CLogger.h"
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Mapping.h"
#include "renderer/backend/gl/ShaderProgram.h"
#include "renderer/backend/gl/Texture.h"
#include
#include
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
bool operator==(const StencilOpState& lhs, const StencilOpState& rhs)
{
return
lhs.failOp == rhs.failOp &&
lhs.passOp == rhs.passOp &&
lhs.depthFailOp == rhs.depthFailOp &&
lhs.compareOp == rhs.compareOp;
}
bool operator!=(const StencilOpState& lhs, const StencilOpState& rhs)
{
return !operator==(lhs, rhs);
}
bool operator==(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return
lhs.x == rhs.x && lhs.y == rhs.y &&
lhs.width == rhs.width && lhs.height == rhs.height;
}
bool operator!=(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return !operator==(lhs, rhs);
}
void ApplyDepthMask(const bool depthWriteEnabled)
{
glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE);
}
void ApplyColorMask(const uint8_t colorWriteMask)
{
glColorMask(
(colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE);
}
void ApplyStencilMask(const uint32_t stencilWriteMask)
{
glStencilMask(stencilWriteMask);
}
GLenum BufferTypeToGLTarget(const CBuffer::Type type)
{
GLenum target = GL_ARRAY_BUFFER;
switch (type)
{
case CBuffer::Type::VERTEX:
target = GL_ARRAY_BUFFER;
break;
case CBuffer::Type::INDEX:
target = GL_ELEMENT_ARRAY_BUFFER;
break;
};
return target;
}
#if !CONFIG2_GLES
bool IsDepthTexture(const Format format)
{
return
format == Format::D16 || format == Format::D24 ||
format == Format::D32 || format == Format::D24_S8;
}
#endif // !CONFIG2_GLES
void UploadDynamicBufferRegionImpl(
const GLenum target, const uint32_t bufferSize,
const uint32_t dataOffset, const uint32_t dataSize,
const CDeviceCommandContext::UploadBufferFunction& uploadFunction)
{
ENSURE(dataOffset < dataSize);
// Tell the driver that it can reallocate the whole VBO
glBufferDataARB(target, bufferSize, nullptr, GL_DYNAMIC_DRAW);
ogl_WarnIfError();
while (true)
{
// (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used
// here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with
// current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do
// the explicit glBufferData.)
void* mappedData = glMapBufferARB(target, GL_WRITE_ONLY);
if (mappedData == nullptr)
{
// This shouldn't happen unless we run out of virtual address space
LOGERROR("glMapBuffer failed");
break;
}
uploadFunction(static_cast(mappedData) + dataOffset);
if (glUnmapBufferARB(target) == GL_TRUE)
break;
// Unmap might fail on e.g. resolution switches, so just try again
// and hope it will eventually succeed
LOGMESSAGE("glUnmapBuffer failed, trying again...\n");
}
}
/**
* In case we don't need a framebuffer content (because of the following clear
* or overwriting by a shader) we might give a hint to a driver via
* glInvalidateFramebuffer.
*/
void InvalidateFramebuffer(
CFramebuffer* framebuffer, const bool color, const bool depthStencil)
{
GLsizei numberOfAttachments = 0;
GLenum attachments[8];
const bool isBackbuffer = framebuffer->GetHandle() == 0;
if (color && (framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT))
{
if (isBackbuffer)
#if CONFIG2_GLES
attachments[numberOfAttachments++] = GL_COLOR_EXT;
#else
attachments[numberOfAttachments++] = GL_COLOR;
#endif
else
attachments[numberOfAttachments++] = GL_COLOR_ATTACHMENT0;
}
if (depthStencil)
{
if (isBackbuffer)
{
if (framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT)
#if CONFIG2_GLES
attachments[numberOfAttachments++] = GL_DEPTH_EXT;
#else
attachments[numberOfAttachments++] = GL_DEPTH;
#endif
if (framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT)
#if CONFIG2_GLES
attachments[numberOfAttachments++] = GL_STENCIL_EXT;
#else
attachments[numberOfAttachments++] = GL_STENCIL;
#endif
}
else
{
if (framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT)
attachments[numberOfAttachments++] = GL_DEPTH_ATTACHMENT;
if (framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT)
attachments[numberOfAttachments++] = GL_STENCIL_ATTACHMENT;
}
}
if (numberOfAttachments > 0)
{
#if CONFIG2_GLES
glDiscardFramebufferEXT(GL_FRAMEBUFFER_EXT, numberOfAttachments, attachments);
#else
glInvalidateFramebuffer(GL_FRAMEBUFFER_EXT, numberOfAttachments, attachments);
#endif
ogl_WarnIfError();
}
}
} // anonymous namespace
// static
std::unique_ptr CDeviceCommandContext::Create(CDevice* device)
{
std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device));
deviceCommandContext->m_Framebuffer = device->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::DONT_CARE,
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::DONT_CARE)->As();
deviceCommandContext->ResetStates();
return deviceCommandContext;
}
CDeviceCommandContext::CDeviceCommandContext(CDevice* device)
: m_Device(device)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
for (BindUnit& unit : m_BoundTextures)
{
unit.target = GL_TEXTURE_2D;
unit.handle = 0;
}
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
m_VertexAttributeFormat[index].active = false;
m_VertexAttributeFormat[index].initialized = false;
m_VertexAttributeFormat[index].bindingSlot = 0;
}
for (size_t index = 0; index < m_BoundBuffers.size(); ++index)
{
const CBuffer::Type type = static_cast(index);
const GLenum target = BufferTypeToGLTarget(type);
const GLuint handle = 0;
m_BoundBuffers[index].first = target;
m_BoundBuffers[index].second = handle;
}
}
CDeviceCommandContext::~CDeviceCommandContext() = default;
IDevice* CDeviceCommandContext::GetDevice()
{
return m_Device;
}
void CDeviceCommandContext::SetGraphicsPipelineState(
const GraphicsPipelineStateDesc& pipelineStateDesc)
{
SetGraphicsPipelineStateImpl(pipelineStateDesc, false);
}
void CDeviceCommandContext::UploadTexture(
ITexture* texture, const Format format,
const void* data, const size_t dataSize,
const uint32_t level, const uint32_t layer)
{
UploadTextureRegion(texture, format, data, dataSize,
0, 0,
std::max(1u, texture->GetWidth() >> level),
std::max(1u, texture->GetHeight() >> level),
level, layer);
}
void CDeviceCommandContext::UploadTextureRegion(
ITexture* destinationTexture, 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)
{
ENSURE(destinationTexture);
CTexture* texture = destinationTexture->As();
ENSURE(texture->GetUsage() & Renderer::Backend::ITexture::Usage::TRANSFER_DST);
ENSURE(width > 0 && height > 0);
if (texture->GetType() == CTexture::Type::TEXTURE_2D)
{
ENSURE(layer == 0);
if (texture->GetFormat() == Format::R8G8B8A8_UNORM ||
texture->GetFormat() == Format::R8G8B8_UNORM ||
#if !CONFIG2_GLES
texture->GetFormat() == Format::R8_UNORM ||
#endif
texture->GetFormat() == Format::A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
size_t bytesPerPixel = 4;
GLenum pixelFormat = GL_RGBA;
switch (dataFormat)
{
case Format::R8G8B8A8_UNORM:
break;
case Format::R8G8B8_UNORM:
pixelFormat = GL_RGB;
bytesPerPixel = 3;
break;
#if !CONFIG2_GLES
case Format::R8_UNORM:
pixelFormat = GL_RED;
bytesPerPixel = 1;
break;
#endif
case Format::A8_UNORM:
pixelFormat = GL_ALPHA;
bytesPerPixel = 1;
break;
case Format::L8_UNORM:
pixelFormat = GL_LUMINANCE;
bytesPerPixel = 1;
break;
default:
debug_warn("Unexpected format.");
break;
}
ENSURE(dataSize == width * height * bytesPerPixel);
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, level,
xOffset, yOffset, width, height,
pixelFormat, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
else if (
texture->GetFormat() == Format::BC1_RGB_UNORM ||
texture->GetFormat() == Format::BC1_RGBA_UNORM ||
texture->GetFormat() == Format::BC2_UNORM ||
texture->GetFormat() == Format::BC3_UNORM)
{
ENSURE(xOffset == 0 && yOffset == 0);
ENSURE(texture->GetFormat() == dataFormat);
// TODO: add data size check.
GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
switch (texture->GetFormat())
{
case Format::BC1_RGBA_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
case Format::BC2_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case Format::BC3_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
break;
}
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE)
{
if (texture->GetFormat() == Format::R8G8B8A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
ENSURE(level == 0 && layer < 6);
ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height);
const size_t bpp = 4;
ENSURE(dataSize == width * height * bpp);
// The order of layers should be the following:
// front, back, top, bottom, right, left
static const GLenum targets[6] =
{
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle());
glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else
debug_warn("Unsupported type");
}
void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize)
{
ENSURE(!m_InsideFramebufferPass);
UploadBufferRegion(buffer, data, dataSize, 0);
}
void CDeviceCommandContext::UploadBuffer(
IBuffer* buffer, const UploadBufferFunction& uploadFunction)
{
ENSURE(!m_InsideFramebufferPass);
UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction);
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(data);
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
ScopedBufferBind scopedBufferBind(this, buffer->As());
if (buffer->IsDynamic())
{
UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, [data, dataSize](u8* mappedData)
{
std::memcpy(mappedData, data, dataSize);
});
}
else
{
glBufferSubDataARB(target, dataOffset, dataSize, data);
ogl_WarnIfError();
}
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
ScopedBufferBind scopedBufferBind(this, buffer->As());
ENSURE(buffer->IsDynamic());
UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, uploadFunction);
}
void CDeviceCommandContext::BeginScopedLabel(const char* name)
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
++m_ScopedLabelDepth;
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name);
}
void CDeviceCommandContext::EndScopedLabel()
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
ENSURE(m_ScopedLabelDepth > 0);
--m_ScopedLabelDepth;
glPopDebugGroup();
}
void CDeviceCommandContext::BindTexture(
const uint32_t unit, const GLenum target, const GLuint handle)
{
ENSURE(unit < m_BoundTextures.size());
#if CONFIG2_GLES
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP);
#else
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE);
#endif
if (m_ActiveTextureUnit != unit)
{
glActiveTexture(GL_TEXTURE0 + unit);
m_ActiveTextureUnit = unit;
}
if (m_BoundTextures[unit].target == target && m_BoundTextures[unit].handle == handle)
return;
if (m_BoundTextures[unit].target != target && m_BoundTextures[unit].target && m_BoundTextures[unit].handle)
glBindTexture(m_BoundTextures[unit].target, 0);
if (m_BoundTextures[unit].handle != handle)
glBindTexture(target, handle);
ogl_WarnIfError();
m_BoundTextures[unit] = {target, handle};
}
void CDeviceCommandContext::BindBuffer(const IBuffer::Type type, CBuffer* buffer)
{
ENSURE(!buffer || buffer->GetType() == type);
if (type == IBuffer::Type::VERTEX)
{
if (m_VertexBuffer == buffer)
return;
m_VertexBuffer = buffer;
}
else if (type == IBuffer::Type::INDEX)
{
if (!buffer)
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
}
const GLenum target = BufferTypeToGLTarget(type);
const GLuint handle = buffer ? buffer->GetHandle() : 0;
glBindBufferARB(target, handle);
ogl_WarnIfError();
const size_t cacheIndex = static_cast(type);
ENSURE(cacheIndex < m_BoundBuffers.size());
m_BoundBuffers[cacheIndex].second = handle;
}
void CDeviceCommandContext::OnTextureDestroy(CTexture* texture)
{
ENSURE(texture);
for (size_t index = 0; index < m_BoundTextures.size(); ++index)
if (m_BoundTextures[index].handle == texture->GetHandle())
BindTexture(index, GL_TEXTURE_2D, 0);
}
void CDeviceCommandContext::Flush()
{
ENSURE(m_ScopedLabelDepth == 0);
GPU_SCOPED_LABEL(this, "CDeviceCommandContext::Flush");
ResetStates();
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
for (size_t unit = 0; unit < m_BoundTextures.size(); ++unit)
{
if (m_BoundTextures[unit].handle)
BindTexture(unit, GL_TEXTURE_2D, 0);
}
BindBuffer(CBuffer::Type::INDEX, nullptr);
BindBuffer(CBuffer::Type::VERTEX, nullptr);
}
void CDeviceCommandContext::ResetStates()
{
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true);
SetScissors(0, nullptr);
m_Framebuffer = m_Device->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::DONT_CARE,
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::DONT_CARE)->As();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_Framebuffer->GetHandle());
ogl_WarnIfError();
}
void CDeviceCommandContext::SetGraphicsPipelineStateImpl(
const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force)
{
ENSURE(!m_InsidePass);
if (m_GraphicsPipelineStateDesc.shaderProgram != pipelineStateDesc.shaderProgram)
{
CShaderProgram* currentShaderProgram = nullptr;
if (m_GraphicsPipelineStateDesc.shaderProgram)
{
currentShaderProgram =
static_cast(m_GraphicsPipelineStateDesc.shaderProgram);
}
CShaderProgram* nextShaderProgram = nullptr;
if (pipelineStateDesc.shaderProgram)
{
nextShaderProgram =
static_cast(pipelineStateDesc.shaderProgram);
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
const VertexAttributeStream stream = static_cast(index);
m_VertexAttributeFormat[index].active = nextShaderProgram->IsStreamActive(stream);
m_VertexAttributeFormat[index].initialized = false;
m_VertexAttributeFormat[index].bindingSlot = std::numeric_limits::max();
}
}
if (nextShaderProgram)
nextShaderProgram->Bind(currentShaderProgram);
else if (currentShaderProgram)
currentShaderProgram->Unbind();
m_ShaderProgram = nextShaderProgram;
}
const DepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState;
const DepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState;
if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled)
{
if (nextDepthStencilStateDesc.depthTestEnabled)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
}
if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp)
{
glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp));
}
if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled)
{
ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled);
}
if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled)
{
if (nextDepthStencilStateDesc.stencilTestEnabled)
glEnable(GL_STENCIL_TEST);
else
glDisable(GL_STENCIL_TEST);
}
if (force ||
currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace ||
currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOp(
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
else
{
if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace)
{
glStencilOpSeparate(
GL_FRONT,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOpSeparate(
GL_BACK,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp));
}
}
}
if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask)
{
ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask);
}
if (force ||
currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference ||
currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask ||
currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp ||
currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
glStencilFunc(
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
else
{
glStencilFuncSeparate(GL_FRONT,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
glStencilFuncSeparate(GL_BACK,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
}
const BlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState;
const BlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState;
if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled)
{
if (nextBlendStateDesc.enabled)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
}
if (force ||
currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor ||
currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor ||
currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor ||
currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor)
{
if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor &&
nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor)
{
glBlendFunc(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor));
}
else
{
glBlendFuncSeparate(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor));
}
}
if (force ||
currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp ||
currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp)
{
if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp)
{
glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp));
}
else
{
glBlendEquationSeparate(
Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp),
Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp));
}
}
if (force ||
currentBlendStateDesc.constant != nextBlendStateDesc.constant)
{
glBlendColor(
nextBlendStateDesc.constant.r,
nextBlendStateDesc.constant.g,
nextBlendStateDesc.constant.b,
nextBlendStateDesc.constant.a);
}
if (force ||
currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask)
{
ApplyColorMask(nextBlendStateDesc.colorWriteMask);
}
const RasterizationStateDesc& currentRasterizationStateDesc =
m_GraphicsPipelineStateDesc.rasterizationState;
const RasterizationStateDesc& nextRasterizationStateDesc =
pipelineStateDesc.rasterizationState;
if (force ||
currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode)
{
#if !CONFIG2_GLES
glPolygonMode(
GL_FRONT_AND_BACK,
nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL);
#endif
}
if (force ||
currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode)
{
if (nextRasterizationStateDesc.cullMode == CullMode::NONE)
{
glDisable(GL_CULL_FACE);
}
else
{
if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE)
glEnable(GL_CULL_FACE);
glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK);
}
}
if (force ||
currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace)
{
if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE)
glFrontFace(GL_CW);
else
glFrontFace(GL_CCW);
}
#if !CONFIG2_GLES
if (force ||
currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled)
{
if (nextRasterizationStateDesc.depthBiasEnabled)
glEnable(GL_POLYGON_OFFSET_FILL);
else
glDisable(GL_POLYGON_OFFSET_FILL);
}
if (force ||
currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor ||
currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor)
{
glPolygonOffset(
nextRasterizationStateDesc.depthBiasSlopeFactor,
nextRasterizationStateDesc.depthBiasConstantFactor);
}
#endif
ogl_WarnIfError();
m_GraphicsPipelineStateDesc = pipelineStateDesc;
}
void CDeviceCommandContext::BlitFramebuffer(
IFramebuffer* dstFramebuffer, IFramebuffer* srcFramebuffer)
{
ENSURE(!m_InsideFramebufferPass);
CFramebuffer* destinationFramebuffer = dstFramebuffer->As();
CFramebuffer* sourceFramebuffer = srcFramebuffer->As();
#if CONFIG2_GLES
UNUSED2(destinationFramebuffer);
UNUSED2(sourceFramebuffer);
debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES");
#else
// Source framebuffer should not be backbuffer.
ENSURE(sourceFramebuffer->GetHandle() != 0);
ENSURE(destinationFramebuffer != sourceFramebuffer);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
// TODO: add more check for internal formats. And currently we don't support
// scaling inside blit.
glBlitFramebufferEXT(
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
(sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()),
GL_NEAREST);
ogl_WarnIfError();
#endif
}
void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil)
{
ENSURE(m_InsideFramebufferPass);
const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0;
const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0;
const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0;
GLbitfield mask = 0;
if (needsColor)
{
ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA);
glClearColor(
m_Framebuffer->GetClearColor().r,
m_Framebuffer->GetClearColor().g,
m_Framebuffer->GetClearColor().b,
m_Framebuffer->GetClearColor().a);
mask |= GL_COLOR_BUFFER_BIT;
}
if (needsDepth)
{
ApplyDepthMask(true);
mask |= GL_DEPTH_BUFFER_BIT;
}
if (needsStencil)
{
ApplyStencilMask(std::numeric_limits::max());
mask |= GL_STENCIL_BUFFER_BIT;
}
glClear(mask);
ogl_WarnIfError();
if (needsColor)
ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask);
if (needsDepth)
ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled);
if (needsStencil)
ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask);
}
void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer)
{
ENSURE(!m_InsideFramebufferPass);
m_InsideFramebufferPass = true;
ENSURE(framebuffer);
m_Framebuffer = framebuffer->As();
ENSURE(m_Framebuffer->GetHandle() == 0 || (m_Framebuffer->GetWidth() > 0 && m_Framebuffer->GetHeight() > 0));
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_Framebuffer->GetHandle());
ogl_WarnIfError();
if (m_Device->UseFramebufferInvalidating())
{
InvalidateFramebuffer(
m_Framebuffer,
m_Framebuffer->GetColorAttachmentLoadOp() != AttachmentLoadOp::LOAD,
m_Framebuffer->GetDepthStencilAttachmentLoadOp() != AttachmentLoadOp::LOAD);
}
const bool needsClearColor =
m_Framebuffer->GetColorAttachmentLoadOp() == AttachmentLoadOp::CLEAR;
const bool needsClearDepthStencil =
m_Framebuffer->GetDepthStencilAttachmentLoadOp() == AttachmentLoadOp::CLEAR;
if (needsClearColor || needsClearDepthStencil)
{
ClearFramebuffer(
needsClearColor, needsClearDepthStencil, needsClearDepthStencil);
}
}
void CDeviceCommandContext::EndFramebufferPass()
{
if (m_Device->UseFramebufferInvalidating())
{
InvalidateFramebuffer(
m_Framebuffer,
m_Framebuffer->GetColorAttachmentStoreOp() != AttachmentStoreOp::STORE,
m_Framebuffer->GetDepthStencilAttachmentStoreOp() != AttachmentStoreOp::STORE);
}
ENSURE(m_InsideFramebufferPass);
m_InsideFramebufferPass = false;
CFramebuffer* framebuffer = m_Device->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::DONT_CARE,
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::DONT_CARE)->As();
if (framebuffer->GetHandle() != m_Framebuffer->GetHandle())
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->GetHandle());
ogl_WarnIfError();
}
m_Framebuffer = framebuffer;
}
void CDeviceCommandContext::ReadbackFramebufferSync(
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
void* data)
{
ENSURE(m_Framebuffer);
glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors)
{
ENSURE(scissorCount <= 1);
if (scissorCount == 0)
{
if (m_ScissorCount != scissorCount)
glDisable(GL_SCISSOR_TEST);
}
else
{
if (m_ScissorCount != scissorCount)
glEnable(GL_SCISSOR_TEST);
ENSURE(scissors);
if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0])
{
m_Scissors[0] = scissors[0];
glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height);
}
}
ogl_WarnIfError();
m_ScissorCount = scissorCount;
}
void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports)
{
+ ENSURE(m_InsideFramebufferPass);
ENSURE(viewportCount == 1);
glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height);
ogl_WarnIfError();
}
void CDeviceCommandContext::SetVertexAttributeFormat(
const VertexAttributeStream stream,
const Format format,
const uint32_t offset,
const uint32_t stride,
const VertexAttributeRate rate,
const uint32_t bindingSlot)
{
const uint32_t index = static_cast(stream);
ENSURE(index < m_VertexAttributeFormat.size());
ENSURE(bindingSlot < m_VertexAttributeFormat.size());
if (!m_VertexAttributeFormat[index].active)
return;
m_VertexAttributeFormat[index].format = format;
m_VertexAttributeFormat[index].offset = offset;
m_VertexAttributeFormat[index].stride = stride;
m_VertexAttributeFormat[index].rate = rate;
m_VertexAttributeFormat[index].bindingSlot = bindingSlot;
m_VertexAttributeFormat[index].initialized = true;
}
void CDeviceCommandContext::SetVertexBuffer(
const uint32_t bindingSlot, IBuffer* buffer, const uint32_t offset)
{
ENSURE(buffer);
ENSURE(buffer->GetType() == IBuffer::Type::VERTEX);
ENSURE(m_ShaderProgram);
BindBuffer(buffer->GetType(), buffer->As());
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot)
continue;
ENSURE(m_VertexAttributeFormat[index].initialized);
const VertexAttributeStream stream = static_cast(index);
m_ShaderProgram->VertexAttribPointer(stream,
m_VertexAttributeFormat[index].format,
m_VertexAttributeFormat[index].offset + offset,
m_VertexAttributeFormat[index].stride,
m_VertexAttributeFormat[index].rate,
nullptr);
}
}
void CDeviceCommandContext::SetVertexBufferData(
const uint32_t bindingSlot, const void* data, const uint32_t dataSize)
{
ENSURE(data);
ENSURE(m_ShaderProgram);
ENSURE(dataSize > 0);
BindBuffer(CBuffer::Type::VERTEX, nullptr);
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot)
continue;
ENSURE(m_VertexAttributeFormat[index].initialized);
const VertexAttributeStream stream = static_cast(index);
// We don't know how many vertices will be used in a draw command, so we
// assume at least one vertex.
ENSURE(dataSize >= m_VertexAttributeFormat[index].offset + m_VertexAttributeFormat[index].stride);
m_ShaderProgram->VertexAttribPointer(stream,
m_VertexAttributeFormat[index].format,
m_VertexAttributeFormat[index].offset,
m_VertexAttributeFormat[index].stride,
m_VertexAttributeFormat[index].rate,
data);
}
}
void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer)
{
ENSURE(buffer->GetType() == CBuffer::Type::INDEX);
m_IndexBuffer = buffer->As();
m_IndexBufferData = nullptr;
BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer);
}
void CDeviceCommandContext::SetIndexBufferData(const void* data, const uint32_t dataSize)
{
ENSURE(dataSize > 0);
if (m_IndexBuffer)
{
BindBuffer(CBuffer::Type::INDEX, nullptr);
m_IndexBuffer = nullptr;
}
m_IndexBufferData = data;
}
void CDeviceCommandContext::BeginPass()
{
ENSURE(!m_InsidePass);
m_InsidePass = true;
}
void CDeviceCommandContext::EndPass()
{
ENSURE(m_InsidePass);
m_InsidePass = false;
}
void CDeviceCommandContext::Draw(
const uint32_t firstVertex, const uint32_t vertexCount)
{
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
// Some drivers apparently don't like count = 0 in glDrawArrays here, so skip
// all drawing in that case.
if (vertexCount == 0)
return;
m_ShaderProgram->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount);
ogl_WarnIfError();
}
void CDeviceCommandContext::DrawIndexed(
const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset)
{
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
ENSURE(vertexOffset == 0);
if (m_IndexBuffer)
{
ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize());
}
m_ShaderProgram->AssertPointersBound();
// Don't use glMultiDrawElements here since it doesn't have a significant
// performance impact and it suffers from various driver bugs (e.g. it breaks
// in Mesa 7.10 swrast with index VBOs).
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT,
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)));
ogl_WarnIfError();
}
void CDeviceCommandContext::DrawInstanced(
const uint32_t firstVertex, const uint32_t vertexCount,
const uint32_t firstInstance, const uint32_t instanceCount)
{
ENSURE(m_Device->GetCapabilities().instancing);
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
if (vertexCount == 0 || instanceCount == 0)
return;
ENSURE(firstInstance == 0);
m_ShaderProgram->AssertPointersBound();
#if CONFIG2_GLES
ENSURE(!m_Device->GetCapabilities().instancing);
UNUSED2(firstVertex);
UNUSED2(vertexCount);
UNUSED2(instanceCount);
#else
glDrawArraysInstancedARB(GL_TRIANGLES, firstVertex, vertexCount, instanceCount);
#endif
ogl_WarnIfError();
}
void CDeviceCommandContext::DrawIndexedInstanced(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t firstInstance, const uint32_t instanceCount,
const int32_t vertexOffset)
{
ENSURE(m_Device->GetCapabilities().instancing);
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
ENSURE(m_IndexBuffer || m_IndexBufferData);
if (indexCount == 0)
return;
ENSURE(firstInstance == 0 && vertexOffset == 0);
if (m_IndexBuffer)
{
ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize());
}
m_ShaderProgram->AssertPointersBound();
// Don't use glMultiDrawElements here since it doesn't have a significant
// performance impact and it suffers from various driver bugs (e.g. it breaks
// in Mesa 7.10 swrast with index VBOs).
#if CONFIG2_GLES
ENSURE(!m_Device->GetCapabilities().instancing);
UNUSED2(indexCount);
UNUSED2(firstIndex);
UNUSED2(instanceCount);
#else
glDrawElementsInstancedARB(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT,
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)),
instanceCount);
#endif
ogl_WarnIfError();
}
void CDeviceCommandContext::DrawIndexedInRange(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end)
{
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
const void* indices =
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex));
m_ShaderProgram->AssertPointersBound();
// Draw with DrawRangeElements where available, since it might be more
// efficient for slow hardware.
#if CONFIG2_GLES
UNUSED2(start);
UNUSED2(end);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);
#else
glDrawRangeElementsEXT(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices);
#endif
ogl_WarnIfError();
}
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
{
ENSURE(m_ShaderProgram);
ENSURE(texture);
ENSURE(texture->GetUsage() & Renderer::Backend::ITexture::Usage::SAMPLED);
const CShaderProgram::TextureUnit textureUnit =
m_ShaderProgram->GetTextureUnit(bindingSlot);
if (!textureUnit.type)
return;
if (textureUnit.type != GL_SAMPLER_2D &&
#if !CONFIG2_GLES
textureUnit.type != GL_SAMPLER_2D_SHADOW &&
#endif
textureUnit.type != GL_SAMPLER_CUBE)
{
LOGERROR("CDeviceCommandContext::SetTexture: expected sampler at binding slot");
return;
}
#if !CONFIG2_GLES
if (textureUnit.type == GL_SAMPLER_2D_SHADOW)
{
if (!IsDepthTexture(texture->GetFormat()))
{
LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture type (expected depth texture)");
return;
}
}
#endif
ENSURE(textureUnit.unit >= 0);
const uint32_t unit = textureUnit.unit;
if (unit >= m_BoundTextures.size())
{
LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture unit (too big)");
return;
}
BindTexture(unit, textureUnit.target, texture->As()->GetHandle());
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float value)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, value);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ)
{
ENSURE(m_ShaderProgram);
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_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot, PS::span values)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, values);
}
CDeviceCommandContext::ScopedBind::ScopedBind(
CDeviceCommandContext* deviceCommandContext,
const GLenum target, const GLuint handle)
: m_DeviceCommandContext(deviceCommandContext),
m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit]),
m_ActiveTextureUnit(deviceCommandContext->m_ActiveTextureUnit)
{
const uint32_t unit = m_DeviceCommandContext->m_BoundTextures.size() - 1;
m_DeviceCommandContext->BindTexture(unit, target, handle);
}
CDeviceCommandContext::ScopedBind::~ScopedBind()
{
m_DeviceCommandContext->BindTexture(
m_ActiveTextureUnit, m_OldBindUnit.target, m_OldBindUnit.handle);
}
CDeviceCommandContext::ScopedBufferBind::ScopedBufferBind(
CDeviceCommandContext* deviceCommandContext, CBuffer* buffer)
: m_DeviceCommandContext(deviceCommandContext)
{
ENSURE(buffer);
m_CacheIndex = static_cast(buffer->GetType());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
const GLuint handle = buffer->GetHandle();
if (m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first == target &&
m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second == handle)
{
// Use an invalid index as a sign that we don't need to restore the
// bound buffer.
m_CacheIndex = m_DeviceCommandContext->m_BoundBuffers.size();
}
else
{
glBindBufferARB(target, handle);
}
}
CDeviceCommandContext::ScopedBufferBind::~ScopedBufferBind()
{
if (m_CacheIndex >= m_DeviceCommandContext->m_BoundBuffers.size())
return;
glBindBufferARB(
m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first,
m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Framebuffer.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Framebuffer.cpp (revision 27312)
+++ ps/trunk/source/renderer/backend/gl/Framebuffer.cpp (revision 27313)
@@ -1,201 +1,204 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Framebuffer.h"
#include "lib/code_annotation.h"
#include "lib/config2.h"
#include "ps/CLogger.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/Texture.h"
namespace Renderer
{
namespace Backend
{
namespace GL
{
// static
std::unique_ptr CFramebuffer::Create(
CDevice* device, const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment)
{
ENSURE(colorAttachment || depthStencilAttachment);
std::unique_ptr framebuffer(new CFramebuffer());
framebuffer->m_Device = device;
if (colorAttachment)
{
framebuffer->m_ClearColor = colorAttachment->clearColor;
framebuffer->m_ColorAttachmentLoadOp = colorAttachment->loadOp;
framebuffer->m_ColorAttachmentStoreOp = colorAttachment->storeOp;
}
if (depthStencilAttachment)
{
framebuffer->m_DepthStencilAttachmentLoadOp = depthStencilAttachment->loadOp;
framebuffer->m_DepthStencilAttachmentStoreOp = depthStencilAttachment->storeOp;
}
glGenFramebuffersEXT(1, &framebuffer->m_Handle);
if (!framebuffer->m_Handle)
{
LOGERROR("Failed to create CFramebuffer object");
return nullptr;
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->m_Handle);
if (colorAttachment)
{
CTexture* colorAttachmentTexture = colorAttachment->texture->As();
ENSURE(device->IsFramebufferFormatSupported(colorAttachmentTexture->GetFormat()));
ENSURE(colorAttachmentTexture->GetUsage() & Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT);
framebuffer->m_AttachmentMask |= GL_COLOR_BUFFER_BIT;
#if CONFIG2_GLES
ENSURE(colorAttachmentTexture->GetType() == CTexture::Type::TEXTURE_2D);
const GLenum textureTarget = GL_TEXTURE_2D;
#else
const GLenum textureTarget = colorAttachmentTexture->GetType() == CTexture::Type::TEXTURE_2D_MULTISAMPLE ?
GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
#endif
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0,
textureTarget, colorAttachmentTexture->GetHandle(), 0);
}
if (depthStencilAttachment)
{
CTexture* depthStencilAttachmentTexture = depthStencilAttachment->texture->As();
ENSURE(depthStencilAttachmentTexture->GetUsage() & Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT);
framebuffer->m_Width = depthStencilAttachmentTexture->GetWidth();
framebuffer->m_Height = depthStencilAttachmentTexture->GetHeight();
framebuffer->m_AttachmentMask |= GL_DEPTH_BUFFER_BIT;
if (depthStencilAttachmentTexture->GetFormat() == Format::D24_S8)
framebuffer->m_AttachmentMask |= GL_STENCIL_BUFFER_BIT;
if (colorAttachment)
{
ENSURE(colorAttachment->texture->GetWidth() == depthStencilAttachmentTexture->GetWidth());
ENSURE(colorAttachment->texture->GetHeight() == depthStencilAttachmentTexture->GetHeight());
ENSURE(colorAttachment->texture->GetType() == depthStencilAttachmentTexture->GetType());
}
ENSURE(
depthStencilAttachmentTexture->GetFormat() == Format::D16 ||
depthStencilAttachmentTexture->GetFormat() == Format::D24 ||
depthStencilAttachmentTexture->GetFormat() == Format::D32 ||
depthStencilAttachmentTexture->GetFormat() == Format::D24_S8);
#if CONFIG2_GLES
ENSURE(depthStencilAttachmentTexture->GetFormat() != Format::D24_S8);
const GLenum attachment = GL_DEPTH_ATTACHMENT;
ENSURE(depthStencilAttachmentTexture->GetType() == CTexture::Type::TEXTURE_2D);
const GLenum textureTarget = GL_TEXTURE_2D;
#else
const GLenum attachment = depthStencilAttachmentTexture->GetFormat() == Format::D24_S8 ?
GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
const GLenum textureTarget = depthStencilAttachmentTexture->GetType() == CTexture::Type::TEXTURE_2D_MULTISAMPLE ?
GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
#endif
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment,
textureTarget, depthStencilAttachmentTexture->GetHandle(), 0);
}
else
{
framebuffer->m_Width = colorAttachment->texture->GetWidth();
framebuffer->m_Height = colorAttachment->texture->GetHeight();
}
ogl_WarnIfError();
#if !CONFIG2_GLES
if (!colorAttachment)
{
glReadBuffer(GL_NONE);
glDrawBuffer(GL_NONE);
}
else
glDrawBuffer(GL_COLOR_ATTACHMENT0);
#endif
ogl_WarnIfError();
#if !CONFIG2_GLES
if (framebuffer->m_Device->GetCapabilities().debugLabels)
{
glObjectLabel(GL_FRAMEBUFFER, framebuffer->m_Handle, -1, name);
}
#else
UNUSED2(name);
#endif
const GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGERROR("CFramebuffer object incomplete: 0x%04X", status);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return nullptr;
}
ogl_WarnIfError();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return framebuffer;
}
// static
std::unique_ptr CFramebuffer::CreateBackbuffer(
CDevice* device,
+ const int surfaceDrawableWidth, const int surfaceDrawableHeight,
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp)
{
// Backbuffer for GL is a special case with a zero framebuffer.
std::unique_ptr framebuffer(new CFramebuffer());
framebuffer->m_Device = device;
framebuffer->m_AttachmentMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
framebuffer->m_ClearColor = CColor(0.0f, 0.0f, 0.0f, 0.0f);
framebuffer->m_ColorAttachmentLoadOp = colorAttachmentLoadOp;
framebuffer->m_ColorAttachmentStoreOp = colorAttachmentStoreOp;
framebuffer->m_DepthStencilAttachmentLoadOp = depthStencilAttachmentLoadOp;
framebuffer->m_DepthStencilAttachmentStoreOp = depthStencilAttachmentStoreOp;
+ framebuffer->m_Width = surfaceDrawableWidth;
+ framebuffer->m_Height = surfaceDrawableHeight;
return framebuffer;
}
CFramebuffer::CFramebuffer() = default;
CFramebuffer::~CFramebuffer()
{
if (m_Handle)
glDeleteFramebuffersEXT(1, &m_Handle);
}
IDevice* CFramebuffer::GetDevice()
{
return m_Device;
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Framebuffer.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/Framebuffer.h (revision 27312)
+++ ps/trunk/source/renderer/backend/gl/Framebuffer.h (revision 27313)
@@ -1,95 +1,96 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_GL_FRAMEBUFFER
#define INCLUDED_RENDERER_BACKEND_GL_FRAMEBUFFER
#include "graphics/Color.h"
#include "lib/ogl.h"
#include "renderer/backend/IFramebuffer.h"
#include
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
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; }
+
GLuint GetHandle() const { return m_Handle; }
GLbitfield GetAttachmentMask() const { return m_AttachmentMask; }
AttachmentLoadOp GetColorAttachmentLoadOp() const { return m_ColorAttachmentLoadOp; }
AttachmentStoreOp GetColorAttachmentStoreOp() const { return m_ColorAttachmentStoreOp; }
AttachmentLoadOp GetDepthStencilAttachmentLoadOp() const { return m_DepthStencilAttachmentLoadOp; }
AttachmentStoreOp GetDepthStencilAttachmentStoreOp() const { return m_DepthStencilAttachmentStoreOp; }
- uint32_t GetWidth() const { return m_Width; }
- uint32_t GetHeight() const { return m_Height; }
-
private:
friend class CDevice;
static std::unique_ptr Create(
CDevice* device, const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment);
static std::unique_ptr CreateBackbuffer(
CDevice* device,
+ const int surfaceDrawableWidth, const int surfaceDrawableHeight,
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp);
CFramebuffer();
CDevice* m_Device = nullptr;
GLuint m_Handle = 0;
uint32_t m_Width = 0, m_Height = 0;
GLbitfield m_AttachmentMask = 0;
CColor m_ClearColor;
AttachmentLoadOp m_ColorAttachmentLoadOp = AttachmentLoadOp::DONT_CARE;
AttachmentStoreOp m_ColorAttachmentStoreOp = AttachmentStoreOp::DONT_CARE;
AttachmentLoadOp m_DepthStencilAttachmentLoadOp = AttachmentLoadOp::DONT_CARE;
AttachmentStoreOp m_DepthStencilAttachmentStoreOp = AttachmentStoreOp::DONT_CARE;
};
} // namespace GL
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_GL_FRAMEBUFFER
Index: ps/trunk/source/renderer/backend/vulkan/Device.cpp
===================================================================
--- ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 27312)
+++ ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 27313)
@@ -1,180 +1,186 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Device.h"
#include "lib/external_libraries/libsdl.h"
#include "scriptinterface/JSON.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptRequest.h"
#if SDL_VERSION_ATLEAST(2, 0, 8)
#include
#endif
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
// static
std::unique_ptr CDevice::Create(SDL_Window* UNUSED(window))
{
std::unique_ptr device(new CDevice());
return device;
}
CDevice::CDevice() = default;
CDevice::~CDevice() = default;
void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
{
Script::SetProperty(rq, settings, "name", "vulkan");
std::string vulkanSupport = "unsupported";
// According to http://wiki.libsdl.org/SDL_Vulkan_LoadLibrary the following
// functionality is supported since SDL 2.0.8.
#if SDL_VERSION_ATLEAST(2, 0, 8)
if (!SDL_Vulkan_LoadLibrary(nullptr))
{
void* vkGetInstanceProcAddr = SDL_Vulkan_GetVkGetInstanceProcAddr();
if (vkGetInstanceProcAddr)
vulkanSupport = "supported";
else
vulkanSupport = "noprocaddr";
SDL_Vulkan_UnloadLibrary();
}
else
{
vulkanSupport = "cantload";
}
#endif
Script::SetProperty(rq, settings, "status", vulkanSupport);
}
std::unique_ptr CDevice::CreateCommandContext()
{
return nullptr;
}
std::unique_ptr CDevice::CreateTexture(
const char* name, const ITexture::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)
{
UNUSED2(name);
UNUSED2(type);
UNUSED2(usage);
UNUSED2(format);
UNUSED2(width);
UNUSED2(height);
UNUSED2(defaultSamplerDesc);
UNUSED2(MIPLevelCount);
UNUSED2(sampleCount);
return nullptr;
}
std::unique_ptr CDevice::CreateTexture2D(
const char* name, 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)
{
return CreateTexture(
name, ITexture::Type::TEXTURE_2D, usage, format,
width, height, defaultSamplerDesc, MIPLevelCount, sampleCount);
}
std::unique_ptr CDevice::CreateFramebuffer(
const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment)
{
UNUSED2(name);
UNUSED2(colorAttachment);
UNUSED2(depthStencilAttachment);
return nullptr;
}
std::unique_ptr CDevice::CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic)
{
UNUSED2(name);
UNUSED2(type);
UNUSED2(size);
UNUSED2(dynamic);
return nullptr;
}
std::unique_ptr CDevice::CreateShaderProgram(
const CStr& name, const CShaderDefines& defines)
{
UNUSED2(name);
UNUSED2(defines);
return nullptr;
}
bool CDevice::AcquireNextBackbuffer()
{
return false;
}
IFramebuffer* CDevice::GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp)
{
UNUSED2(colorAttachmentLoadOp);
UNUSED2(colorAttachmentStoreOp);
UNUSED2(depthStencilAttachmentLoadOp);
UNUSED2(depthStencilAttachmentStoreOp);
return nullptr;
}
void CDevice::Present()
{
}
+void CDevice::OnWindowResize(const uint32_t width, const uint32_t height)
+{
+ UNUSED2(width);
+ UNUSED2(height);
+}
+
bool CDevice::IsTextureFormatSupported(const Format format) const
{
UNUSED2(format);
return false;
}
bool CDevice::IsFramebufferFormatSupported(const Format format) const
{
UNUSED2(format);
return false;
}
std::unique_ptr CreateDevice(SDL_Window* window)
{
return Vulkan::CDevice::Create(window);
}
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/vulkan/Device.h
===================================================================
--- ps/trunk/source/renderer/backend/vulkan/Device.h (revision 27312)
+++ ps/trunk/source/renderer/backend/vulkan/Device.h (revision 27313)
@@ -1,112 +1,114 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE
#define INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE
#include "renderer/backend/IDevice.h"
#include "renderer/backend/vulkan/DeviceForward.h"
#include "scriptinterface/ScriptForward.h"
#include
typedef struct SDL_Window SDL_Window;
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
class CDevice : public IDevice
{
public:
/**
* Creates the Vulkan device.
*/
static std::unique_ptr Create(SDL_Window* window);
~CDevice() override;
Backend GetBackend() const override { return Backend::VULKAN; }
const std::string& GetName() const override { return m_Name; }
const std::string& GetVersion() const override { return m_Version; }
const std::string& GetDriverInformation() const override { return m_DriverInformation; }
const std::vector& GetExtensions() const override { return m_Extensions; }
void Report(const ScriptRequest& rq, JS::HandleValue settings) override;
std::unique_ptr CreateCommandContext() override;
std::unique_ptr CreateTexture(
const char* name, const ITexture::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) override;
std::unique_ptr CreateTexture2D(
const char* name, const uint32_t usage,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1) override;
std::unique_ptr CreateFramebuffer(
const char* name, SColorAttachment* colorAttachment,
SDepthStencilAttachment* depthStencilAttachment) override;
std::unique_ptr CreateBuffer(
const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) override;
std::unique_ptr CreateShaderProgram(
const CStr& name, const CShaderDefines& defines) override;
bool AcquireNextBackbuffer() override;
IFramebuffer* GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
void Present() override;
+ void OnWindowResize(const uint32_t width, const uint32_t height) override;
+
bool IsTextureFormatSupported(const Format format) const override;
bool IsFramebufferFormatSupported(const Format format) const override;
const Capabilities& GetCapabilities() const override { return m_Capabilities; }
private:
CDevice();
std::string m_Name;
std::string m_Version;
std::string m_DriverInformation;
std::vector m_Extensions;
Capabilities m_Capabilities{};
};
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_VULKAN_DEVICE
Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 27312)
+++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 27313)
@@ -1,583 +1,589 @@
/* Copyright (C) 2022 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 |