Removes ARB (legacy OpenGL) backend

ARB is a legacy backend which uses old assembly shaders. It makes
writing shaders more complex. According to the stats
https://feedback.wildfiregames.com we small amount of players with
ARB.

Fixes #8533
This commit is contained in:
Vladislav Belov 2026-02-19 18:20:09 +01:00
parent 963fed0036
commit 0432b86de7
No known key found for this signature in database
GPG key ID: B4FB505BA342FB94
17 changed files with 742 additions and 1500 deletions

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -222,8 +222,6 @@ bool CShaderManager::LoadTechnique(CShaderTechniquePtr& tech)
XMBElement root = XeroFile.GetRoot();
PS::StaticVector<std::string_view, 3> supportedShaders;
if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
supportedShaders.emplace_back("arb");
if (m_Device->GetBackend() == Renderer::Backend::Backend::GL)
supportedShaders.emplace_back("glsl");
if (m_Device->GetBackend() == Renderer::Backend::Backend::VULKAN)

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -65,7 +65,6 @@ X(RENDER_DEBUG_MODE_NONE)
X(SHADOWS_CASCADE_COUNT)
X(USE_DESCRIPTOR_INDEXING)
X(USE_FANCY_EFFECTS)
X(USE_FP_SHADOW)
X(USE_GPU_INSTANCING)
X(USE_INSTANCING)
X(USE_NORMALS)

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -89,9 +89,6 @@ Renderer::Backend::Backend GetFallbackBackend(const Renderer::Backend::Backend b
switch (backend)
{
case Renderer::Backend::Backend::GL:
fallback = Renderer::Backend::Backend::GL_ARB;
break;
case Renderer::Backend::Backend::GL_ARB:
fallback = Renderer::Backend::Backend::DUMMY;
break;
case Renderer::Backend::Backend::DUMMY:
@ -111,9 +108,6 @@ std::string_view GetBackendName(const Renderer::Backend::Backend backend)
case Renderer::Backend::Backend::GL:
name = "GL";
break;
case Renderer::Backend::Backend::GL_ARB:
name = "GL ARB";
break;
case Renderer::Backend::Backend::DUMMY:
name = "Dummy";
break;
@ -306,9 +300,7 @@ void CVideoMode::ReadConfig()
m_ConfigVSync = g_ConfigDB.Get("vsync", m_ConfigVSync);
const std::string rendererBackend{g_ConfigDB.Get("rendererbackend", std::string{})};
if (rendererBackend == "glarb")
m_Backend = Renderer::Backend::Backend::GL_ARB;
else if (rendererBackend == "dummy")
if (rendererBackend == "dummy")
m_Backend = Renderer::Backend::Backend::DUMMY;
else if (rendererBackend == "vulkan")
m_Backend = Renderer::Backend::Backend::VULKAN;
@ -334,9 +326,7 @@ bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
if (!m_Window)
{
const bool isGLBackend =
m_Backend == Renderer::Backend::Backend::GL ||
m_Backend == Renderer::Backend::Backend::GL_ARB;
const bool isGLBackend{m_Backend == Renderer::Backend::Backend::GL};
if (isGLBackend)
{
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
@ -595,7 +585,7 @@ bool CVideoMode::InitSDL()
// 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.)
if (m_Backend == Renderer::Backend::Backend::GL || m_Backend == Renderer::Backend::Backend::GL_ARB)
if (m_Backend == Renderer::Backend::Backend::GL)
atexit(SDL_Quit);
// End work around.
@ -660,10 +650,7 @@ bool CVideoMode::TryCreateBackendDevice(SDL_Window* window)
switch (m_Backend)
{
case Renderer::Backend::Backend::GL:
m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, false);
break;
case Renderer::Backend::Backend::GL_ARB:
m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, true);
m_BackendDevice = Renderer::Backend::GL::CreateDevice(window);
break;
case Renderer::Backend::Backend::DUMMY:
m_BackendDevice = Renderer::Backend::Dummy::CreateDevice(window);

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -59,11 +59,11 @@ struct IModelDef : public CModelDefRPrivate
/// Indices are the same for all models, so share them
VertexIndexArray m_IndexArray;
IModelDef(const CModelDefPtr& mdef, bool calculateTangents);
IModelDef(const CModelDefPtr& mdef);
};
IModelDef::IModelDef(const CModelDefPtr& mdef, bool calculateTangents)
IModelDef::IModelDef(const CModelDefPtr& mdef)
: m_IndexArray(Renderer::Backend::IBuffer::Usage::TRANSFER_DST),
m_Array(Renderer::Backend::IBuffer::Type::VERTEX, Renderer::Backend::IBuffer::Usage::TRANSFER_DST)
{
@ -82,116 +82,86 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool calculateTangents)
m_Array.AddAttribute(&m_UVs[i]);
}
if (calculateTangents)
// Generate tangents for the geometry:-
m_Tangent.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT;
m_Array.AddAttribute(&m_Tangent);
// floats per vertex; position + normal + tangent + UV*sets
int numVertexAttrs = 3 + 3 + 4 + 2 * mdef->GetNumUVsPerVertex();
// the tangent generation can increase the number of vertices temporarily
// so reserve a bit more memory to avoid reallocations in GenTangents (in most cases)
std::vector<float> newVertices;
newVertices.reserve(numVertexAttrs * numVertices * 2);
// Generate the tangents
ModelRenderer::GenTangents(mdef, newVertices, false);
// how many vertices do we have after generating tangents?
int newNumVert = newVertices.size() / numVertexAttrs;
std::vector<int> remapTable(newNumVert);
std::vector<float> vertexDataOut(newNumVert * numVertexAttrs);
// re-weld the mesh to remove duplicated vertices
int numVertices2 = WeldMesh(&remapTable[0], &vertexDataOut[0],
&newVertices[0], newNumVert, numVertexAttrs);
// Copy the model data to graphics memory:-
m_Array.SetNumberOfVertices(numVertices2);
m_Array.Layout();
VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>();
VertexArrayIterator<CVector3D> Normal = m_Normal.GetIterator<CVector3D>();
VertexArrayIterator<CVector4D> Tangent = m_Tangent.GetIterator<CVector4D>();
// copy everything into the vertex array
for (int i = 0; i < numVertices2; i++)
{
// Generate tangents for the geometry:-
int q = numVertexAttrs * i;
m_Tangent.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT;
m_Array.AddAttribute(&m_Tangent);
Position[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]);
q += 3;
// floats per vertex; position + normal + tangent + UV*sets
int numVertexAttrs = 3 + 3 + 4 + 2 * mdef->GetNumUVsPerVertex();
Normal[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]);
q += 3;
// the tangent generation can increase the number of vertices temporarily
// so reserve a bit more memory to avoid reallocations in GenTangents (in most cases)
std::vector<float> newVertices;
newVertices.reserve(numVertexAttrs * numVertices * 2);
Tangent[i] = CVector4D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2],
vertexDataOut[q + 3]);
q += 4;
// Generate the tangents
ModelRenderer::GenTangents(mdef, newVertices, false);
// how many vertices do we have after generating tangents?
int newNumVert = newVertices.size() / numVertexAttrs;
std::vector<int> remapTable(newNumVert);
std::vector<float> vertexDataOut(newNumVert * numVertexAttrs);
// re-weld the mesh to remove duplicated vertices
int numVertices2 = WeldMesh(&remapTable[0], &vertexDataOut[0],
&newVertices[0], newNumVert, numVertexAttrs);
// Copy the model data to graphics memory:-
m_Array.SetNumberOfVertices(numVertices2);
m_Array.Layout();
VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>();
VertexArrayIterator<CVector3D> Normal = m_Normal.GetIterator<CVector3D>();
VertexArrayIterator<CVector4D> Tangent = m_Tangent.GetIterator<CVector4D>();
// copy everything into the vertex array
for (int i = 0; i < numVertices2; i++)
for (size_t j = 0; j < mdef->GetNumUVsPerVertex(); j++)
{
int q = numVertexAttrs * i;
Position[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]);
q += 3;
Normal[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]);
q += 3;
Tangent[i] = CVector4D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2],
vertexDataOut[q + 3]);
q += 4;
for (size_t j = 0; j < mdef->GetNumUVsPerVertex(); j++)
{
VertexArrayIterator<float[2]> UVit = m_UVs[j].GetIterator<float[2]>();
UVit[i][0] = vertexDataOut[q + 0 + 2 * j];
UVit[i][1] = vertexDataOut[q + 1 + 2 * j];
}
VertexArrayIterator<float[2]> UVit = m_UVs[j].GetIterator<float[2]>();
UVit[i][0] = vertexDataOut[q + 0 + 2 * j];
UVit[i][1] = vertexDataOut[q + 1 + 2 * j];
}
// upload vertex data
m_Array.Upload();
m_Array.FreeBackingStore();
m_IndexArray.SetNumberOfVertices(mdef->GetNumFaces() * 3);
m_IndexArray.Layout();
VertexArrayIterator<u16> Indices = m_IndexArray.GetIterator();
size_t idxidx = 0;
// reindex geometry and upload index
for (size_t j = 0; j < mdef->GetNumFaces(); ++j)
{
Indices[idxidx++] = remapTable[j * 3 + 0];
Indices[idxidx++] = remapTable[j * 3 + 1];
Indices[idxidx++] = remapTable[j * 3 + 2];
}
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
}
else
// upload vertex data
m_Array.Upload();
m_Array.FreeBackingStore();
m_IndexArray.SetNumberOfVertices(mdef->GetNumFaces() * 3);
m_IndexArray.Layout();
VertexArrayIterator<u16> Indices = m_IndexArray.GetIterator();
size_t idxidx = 0;
// reindex geometry and upload index
for (size_t j = 0; j < mdef->GetNumFaces(); ++j)
{
// Upload model without calculating tangents:-
m_Array.SetNumberOfVertices(numVertices);
m_Array.Layout();
VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>();
VertexArrayIterator<CVector3D> Normal = m_Normal.GetIterator<CVector3D>();
ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal);
for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++)
{
VertexArrayIterator<float[2]> UVit = m_UVs[i].GetIterator<float[2]>();
ModelRenderer::BuildUV(mdef, UVit, i);
}
m_Array.Upload();
m_Array.FreeBackingStore();
m_IndexArray.SetNumberOfVertices(mdef->GetNumFaces()*3);
m_IndexArray.Layout();
ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator());
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
Indices[idxidx++] = remapTable[j * 3 + 0];
Indices[idxidx++] = remapTable[j * 3 + 1];
Indices[idxidx++] = remapTable[j * 3 + 2];
}
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
const uint32_t stride = m_Array.GetStride();
constexpr size_t MAX_UV = 2;
@ -214,31 +184,25 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool calculateTangents)
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0});
}
if (calculateTangents)
{
attributes.push_back({
Renderer::Backend::VertexAttributeStream::UV2,
m_Tangent.format, m_Tangent.offset, stride,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0});
}
attributes.push_back({
Renderer::Backend::VertexAttributeStream::UV2,
m_Tangent.format, m_Tangent.offset, stride,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0});
m_VertexInputLayout = g_Renderer.GetVertexInputLayout({attributes.begin(), attributes.end()});
}
struct InstancingModelRendererInternals
{
bool calculateTangents;
/// Previously prepared modeldef
IModelDef* imodeldef;
};
// Construction and Destruction
InstancingModelRenderer::InstancingModelRenderer(bool calculateTangents)
InstancingModelRenderer::InstancingModelRenderer()
{
m = new InstancingModelRendererInternals;
m->calculateTangents = calculateTangents;
m->imodeldef = 0;
}
@ -258,7 +222,7 @@ CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* m
if (!imodeldef)
{
imodeldef = new IModelDef(mdef, m->calculateTangents);
imodeldef = new IModelDef(mdef);
mdef->SetRenderData(m, imodeldef);
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -37,7 +37,7 @@ struct InstancingModelRendererInternals;
class InstancingModelRenderer : public ModelVertexRenderer
{
public:
InstancingModelRenderer(bool calculateTangents);
InstancingModelRenderer();
~InstancingModelRenderer();
// Implementations

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -115,9 +115,7 @@ bool CPostprocManager::IsEnabled() const
Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT, true, true)
!= Renderer::Backend::Format::UNDEFINED;
return
g_RenderingOptions.GetPostProc() &&
m_Device->GetBackend() != Renderer::Backend::Backend::GL_ARB &&
isDepthStencilFormatPresent;
g_RenderingOptions.GetPostProc() && isDepthStencilFormatPresent;
}
void CPostprocManager::Cleanup()
@ -701,10 +699,9 @@ void CPostprocManager::ApplyPostproc(
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 = m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB;
const bool hasAA = m_AATech && !hasARB;
const bool hasSharp = m_SharpTech && !hasARB;
const bool hasEffects{m_PostProcEffect != L"default"};
const bool hasAA{m_AATech};
const bool hasSharp{m_SharpTech};
if (!hasEffects && !hasAA && !hasSharp)
return;
@ -773,7 +770,7 @@ void CPostprocManager::SetPostEffect(const CStrW& name)
void CPostprocManager::UpdateAntiAliasingTechnique()
{
if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
if (!m_IsInitialized)
return;
const std::string newAAName{g_ConfigDB.Get("antialiasing", std::string{})};
@ -822,7 +819,7 @@ void CPostprocManager::UpdateAntiAliasingTechnique()
void CPostprocManager::UpdateSharpeningTechnique()
{
if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
if (!m_IsInitialized)
return;
const std::string newSharpName{g_ConfigDB.Get("sharpening", std::string{})};
@ -943,11 +940,6 @@ void CPostprocManager::ResolveMultisampleFramebuffer(
void CPostprocManager::RecalculateSize(const uint32_t width, const uint32_t height)
{
if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
{
m_Scale = 1.0f;
return;
}
m_Scale = g_ConfigDB.Get("renderer.scale", m_Scale);
if (m_Scale < 0.25f || m_Scale > 2.0f)
{

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -42,7 +42,7 @@ enum RenderPath
// Classic fixed function.
FIXED,
// Use new ARB/GLSL system
// Use new GLSL system
SHADER
};

View file

@ -214,15 +214,13 @@ CSceneRenderer::~CSceneRenderer()
m.reset();
}
void CSceneRenderer::ReloadShaders(Renderer::Backend::IDevice* device)
void CSceneRenderer::ReloadShaders([[maybe_unused]] Renderer::Backend::IDevice* device)
{
m->globalContext = CShaderDefines();
if (g_RenderingOptions.GetShadows())
{
m->globalContext.Add(str_USE_SHADOW, str_1);
if (device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
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();
@ -245,7 +243,7 @@ void CSceneRenderer::ReloadShaders(Renderer::Backend::IDevice* device)
ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED);
m->Model.VertexRendererShader = ModelVertexRendererPtr(new CPUSkinnedModelVertexRenderer());
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(device->GetBackend() != Renderer::Backend::Backend::GL_ARB));
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer());
if (g_RenderingOptions.GetGPUSkinning())
{

View file

@ -139,7 +139,7 @@ void ShadowMapInternals::UpdateCascadesParameters()
{
CascadeCount = g_ConfigDB.Get("shadowscascadecount", 1);
if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || Device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT)
CascadeCount = 1;
ShadowsCoverMap = g_ConfigDB.Get("shadowscovermap", false);

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -1165,9 +1165,7 @@ void WaterManager::UpdateQuality()
bool WaterManager::WillRenderFancyWater() const
{
return
m_RenderWater && m_Device->GetBackend() != Renderer::Backend::Backend::GL_ARB &&
g_RenderingOptions.GetWaterEffects();
return m_RenderWater && g_RenderingOptions.GetWaterEffects();
}
size_t WaterManager::GetCurrentTextureIndex(const double& period) const

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,7 +27,6 @@ namespace Backend
enum class Backend
{
GL,
GL_ARB,
VULKAN,
DUMMY
};

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -225,7 +225,7 @@ void GLAD_API_PTR OnDebugMessage(
} // anonymous namespace
// static
std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window)
{
std::unique_ptr<CDevice> device(new CDevice());
@ -320,17 +320,8 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
#endif
}
// If we don't have GL2.0 then we don't have GLSL in core.
if (!arb && !ogl_HaveVersion(2, 0))
return nullptr;
// We can't render without ARB shaders.
if (arb && ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr))
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
if (!ogl_HaveVersion(2, 0)
|| !ogl_HaveExtension("GL_ARB_vertex_buffer_object")
|| ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", nullptr)
|| (!ogl_HaveExtension("GL_EXT_framebuffer_object") && !ogl_HaveExtension("GL_ARB_framebuffer_object")))
{
@ -344,8 +335,6 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
);
}
device->m_ARB = arb;
device->m_Name = GetNameImpl();
device->m_Version = GetVersionImpl();
device->m_DriverInformation = GetDriverInformationImpl();
@ -360,26 +349,18 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
// still support pre 2.0 drivers pretending to support 2.0.
ogl_SquelchError(GL_INVALID_ENUM);
if (arb)
{
#if !CONFIG2_GLES
glEnable(GL_VERTEX_PROGRAM_ARB);
glEnable(GL_FRAGMENT_PROGRAM_ARB);
#endif
}
// Some drivers might invalidate an incorrect surface which leads to artifacts.
if (g_ConfigDB.Get("renderer.backend.gl.enableframebufferinvalidating", false))
{
#if CONFIG2_GLES
device->m_UseFramebufferInvalidating = ogl_HaveExtension("GL_EXT_discard_framebuffer");
#else
device->m_UseFramebufferInvalidating = !arb && ogl_HaveExtension("GL_ARB_invalidate_subdata");
device->m_UseFramebufferInvalidating = ogl_HaveExtension("GL_ARB_invalidate_subdata");
#endif
}
Capabilities& capabilities = device->m_Capabilities;
capabilities.computeShaders = !device->m_ARB && ogl_HaveVersion(4, 3);
capabilities.computeShaders = ogl_HaveVersion(4, 3);
#if CONFIG2_GLES
// Some GLES implementations have GL_EXT_texture_compression_dxt1
// but that only supports DXT1 so we can't use it.
@ -459,7 +440,6 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
capabilities.timestamps = false;
#else
capabilities.instancing =
!device->m_ARB &&
(ogl_HaveVersion(3, 3) ||
(ogl_HaveExtension("GL_ARB_draw_instanced") &&
ogl_HaveExtension("GL_ARB_instanced_arrays")));
@ -480,7 +460,7 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window, const bool arb)
&& ogl_HaveExtension("GL_ARB_shader_storage_buffer_object")
&& ogl_HaveExtension("GL_ARB_half_float_vertex")
&& ogl_HaveExtension("GL_ARB_program_interface_query");
capabilities.timestamps = !device->m_ARB && ogl_HaveExtension("GL_ARB_timer_query");
capabilities.timestamps = ogl_HaveExtension("GL_ARB_timer_query");
if (capabilities.timestamps)
capabilities.timestampMultiplier = 1.0 / 1e9;
#endif
@ -509,7 +489,7 @@ void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
{
const char* errstr = "(error)";
Script::SetProperty(rq, settings, "name", m_ARB ? "glarb" : "gl");
Script::SetProperty(rq, settings, "name", "gl");
#define INTEGER(id) do { \
GLint i = -1; \
@ -569,24 +549,6 @@ void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
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();
@ -728,75 +690,6 @@ void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
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);
@ -1178,9 +1071,9 @@ void CDevice::InsertTimestampQuery(const uint32_t handle)
#endif
}
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window, const bool arb)
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window)
{
return GL::CDevice::Create(window, arb);
return GL::CDevice::Create(window);
}
} // namespace GL

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -60,9 +60,9 @@ public:
/**
* Creates the GL device and the GL context for the window if it presents.
*/
static std::unique_ptr<IDevice> Create(SDL_Window* window, const bool arb);
static std::unique_ptr<IDevice> Create(SDL_Window* window);
Backend GetBackend() const override { return m_ARB ? Backend::GL_ARB : Backend::GL; }
Backend GetBackend() const override { return Backend::GL; }
const std::string& GetName() const override { return m_Name; }
const std::string& GetVersion() const override { return m_Version; }
@ -144,8 +144,6 @@ private:
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;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -33,7 +33,7 @@ class IDevice;
namespace GL
{
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window, const bool arb);
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window);
} // namespace GL

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,13 +21,20 @@
#include "lib/code_annotation.h"
#include "lib/debug.h"
#include "lib/ogl.h"
#include "ps/containers/StaticVector.h"
#include "ps/CStr.h"
#include "ps/CStrIntern.h"
#include "renderer/backend/Format.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/IBuffer.h"
#include "renderer/backend/IShaderProgram.h"
#include <cstdint>
#include <map>
#include <memory>
#include <span>
#include <tuple>
#include <unordered_map>
#include <vector>
class CShaderDefines;
@ -70,19 +77,10 @@ private:
/**
* A compiled vertex+fragment shader program.
* The implementation may use GL_ARB_{vertex,fragment}_program (ARB assembly syntax)
* or GL_ARB_{vertex,fragment}_shader (GLSL), or may use hard-coded fixed-function
* multitexturing setup code; the difference is hidden from the caller.
*
* Texture/uniform IDs are typically strings, corresponding to the names defined in
* the shader .xml file. Alternatively (and more efficiently, if used very frequently),
* call GetBindingSlot and pass its return value as the ID.
* Setting uniforms that the shader .xml doesn't support is harmless.
*
* For a high-level overview of shaders and materials, see
* https://gitea.wildfiregames.com/0ad/0ad/wiki/MaterialSystem
* Setting uniforms that the shader doesn't support is harmless.
*/
class CShaderProgram : public IShaderProgram
class CShaderProgram final : public IShaderProgram
{
NONCOPYABLE(CShaderProgram);
@ -98,12 +96,14 @@ public:
* Binds the shader into the GL context. Call this before calling Uniform()
* or trying to render with it.
*/
virtual void Bind(CShaderProgram* previousShaderProgram) = 0;
void Bind(CShaderProgram* previousShaderProgram);
/**
* Unbinds the shader from the GL context. Call this after rendering with it.
*/
virtual void Unbind() = 0;
void Unbind();
int32_t GetBindingSlot(const CStrIntern name) const override;
struct TextureUnit
{
@ -111,29 +111,29 @@ public:
GLenum target;
GLint unit;
};
virtual TextureUnit GetTextureUnit(const int32_t bindingSlot) = 0;
TextureUnit GetTextureUnit(const int32_t bindingSlot);
virtual GLuint GetStorageBuffer(const int32_t bindingSlot) = 0;
GLuint GetStorageBuffer(const int32_t bindingSlot);
virtual void SetUniform(
void SetUniform(
const int32_t bindingSlot,
const float value) = 0;
virtual void SetUniform(
const float value);
void SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY) = 0;
virtual void SetUniform(
const float valueX, const float valueY);
void SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ) = 0;
virtual void SetUniform(
const float valueZ);
void SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ, const float valueW) = 0;
virtual void SetUniform(
const int32_t bindingSlot, std::span<const float> values) = 0;
const float valueZ, const float valueW);
void SetUniform(
const int32_t bindingSlot, std::span<const float> values);
// Vertex attribute pointers (equivalent to glVertexPointer etc).
virtual void VertexAttribPointer(
void VertexAttribPointer(
const VertexAttributeStream stream, const Format format,
const uint32_t offset, const uint32_t stride,
const VertexAttributeRate rate, const void* data);
@ -142,28 +142,69 @@ public:
bool HasImageUniforms() const { return m_HasImageUniforms; }
IDevice* GetDevice() override { return m_Device; }
std::vector<VfsPath> GetFileDependencies() const override;
/**
* Checks that all the required vertex attributes have been set.
* Call this before calling Draw/DrawIndexed etc to avoid potential crashes.
*/
void AssertPointersBound();
protected:
CShaderProgram(int streamflags);
private:
CShaderProgram(
CDevice* device, const CStr& name, const VfsPath& programPath,
std::span<const std::tuple<VfsPath, GLenum>> shaderStages,
const CShaderDefines& defines,
const std::map<CStrIntern, int>& vertexAttribs,
int streamflags);
void VertexPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer);
void NormalPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer);
void ColorPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer);
void TexCoordPointer(GLenum texture, const Renderer::Backend::Format format, GLsizei stride, const void* pointer);
bool Link(const VfsPath& path);
int m_StreamFlags;
// Non-GLSL client state handling:
void BindClientStates();
void UnbindClientStates();
int m_ValidStreams; // which streams have been specified via VertexPointer etc since the last Bind
bool m_HasImageUniforms{false};
struct ShaderStage
{
GLenum type;
GLuint shader;
};
CDevice* m_Device{nullptr};
CStr m_Name;
std::vector<VfsPath> m_FileDependencies;
std::map<CStrIntern, int> m_VertexAttribs;
// Sorted list of active vertex attributes.
std::vector<int> m_ActiveVertexAttributes;
GLuint m_Program;
// 5 = max(compute, vertex + tesselation (control + evaluation) + geometry + fragment).
PS::StaticVector<ShaderStage, 5> m_ShaderStages;
struct BindingSlot
{
CStrIntern name;
GLint location;
GLint offset;
GLint size;
GLenum type;
GLenum elementType;
GLint elementCount;
bool isTexture;
bool isStorageBuffer;
};
std::vector<BindingSlot> m_BindingSlots;
std::unordered_map<CStrIntern, int32_t> m_BindingSlotsMapping;
GLint m_UniformBufferLocation{-1};
uint32_t m_UniformBufferSize{0};
std::unique_ptr<IBuffer> m_UniformBuffer;
};
} // namespace GL