mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Avoid cases of filenames Update years in terms and other legal(ish) documents Don't update years in license headers, since change is not meaningful Will add linter rule in seperate commit Happy recompiling everyone! Original Patch By: Nescio Comment By: Gallaecio Differential Revision: D2620 This was SVN commit r27786.
1512 lines
46 KiB
C++
1512 lines
46 KiB
C++
/* Copyright (C) 2023 Wildfire Games.
|
||
* This file is part of 0 A.D.
|
||
*
|
||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, either version 2 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* 0 A.D. is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "precompiled.h"
|
||
|
||
#include "ShaderProgram.h"
|
||
|
||
#include "graphics/Color.h"
|
||
#include "graphics/PreprocessorWrapper.h"
|
||
#include "graphics/ShaderManager.h"
|
||
#include "graphics/TextureManager.h"
|
||
#include "ps/CLogger.h"
|
||
#include "ps/containers/StaticVector.h"
|
||
#include "ps/Filesystem.h"
|
||
#include "ps/Profile.h"
|
||
#include "ps/XML/Xeromyces.h"
|
||
#include "renderer/backend/gl/Device.h"
|
||
#include "renderer/backend/gl/DeviceCommandContext.h"
|
||
|
||
#define USE_SHADER_XML_VALIDATION 1
|
||
|
||
#if USE_SHADER_XML_VALIDATION
|
||
#include "ps/XML/RelaxNG.h"
|
||
#include "ps/XML/XMLWriter.h"
|
||
#endif
|
||
|
||
#include <algorithm>
|
||
#include <map>
|
||
#include <tuple>
|
||
#include <unordered_map>
|
||
|
||
namespace Renderer
|
||
{
|
||
|
||
namespace Backend
|
||
{
|
||
|
||
namespace GL
|
||
{
|
||
|
||
namespace
|
||
{
|
||
|
||
struct Binding
|
||
{
|
||
Binding(int a, int b) : first(a), second(b) { }
|
||
|
||
Binding() : first(-1), second(-1) { }
|
||
|
||
/**
|
||
* Returns whether this uniform attribute is active in the shader.
|
||
* If not then there's no point calling Uniform() to set its value.
|
||
*/
|
||
bool Active() const { return first != -1 || second != -1; }
|
||
|
||
int first;
|
||
int second;
|
||
};
|
||
|
||
int GetStreamMask(const VertexAttributeStream stream)
|
||
{
|
||
return 1 << static_cast<int>(stream);
|
||
}
|
||
|
||
GLint GLSizeFromFormat(const Format format)
|
||
{
|
||
GLint size = 1;
|
||
if (format == Renderer::Backend::Format::R32_SFLOAT ||
|
||
format == Renderer::Backend::Format::R16_SINT)
|
||
size = 1;
|
||
else if (
|
||
format == Renderer::Backend::Format::R8G8_UNORM ||
|
||
format == Renderer::Backend::Format::R8G8_UINT ||
|
||
format == Renderer::Backend::Format::R16G16_SINT ||
|
||
format == Renderer::Backend::Format::R32G32_SFLOAT)
|
||
size = 2;
|
||
else if (format == Renderer::Backend::Format::R32G32B32_SFLOAT)
|
||
size = 3;
|
||
else if (
|
||
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT ||
|
||
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
|
||
format == Renderer::Backend::Format::R8G8B8A8_UINT)
|
||
size = 4;
|
||
else
|
||
debug_warn("Unsupported format.");
|
||
return size;
|
||
}
|
||
|
||
GLenum GLTypeFromFormat(const Format format)
|
||
{
|
||
GLenum type = GL_FLOAT;
|
||
if (format == Renderer::Backend::Format::R32_SFLOAT ||
|
||
format == Renderer::Backend::Format::R32G32_SFLOAT ||
|
||
format == Renderer::Backend::Format::R32G32B32_SFLOAT ||
|
||
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT)
|
||
type = GL_FLOAT;
|
||
else if (
|
||
format == Renderer::Backend::Format::R16_SINT ||
|
||
format == Renderer::Backend::Format::R16G16_SINT)
|
||
type = GL_SHORT;
|
||
else if (
|
||
format == Renderer::Backend::Format::R8G8_UNORM ||
|
||
format == Renderer::Backend::Format::R8G8_UINT ||
|
||
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
|
||
format == Renderer::Backend::Format::R8G8B8A8_UINT)
|
||
type = GL_UNSIGNED_BYTE;
|
||
else
|
||
debug_warn("Unsupported format.");
|
||
return type;
|
||
}
|
||
|
||
GLboolean NormalizedFromFormat(const Format format)
|
||
{
|
||
switch (format)
|
||
{
|
||
case Format::R8G8_UNORM: FALLTHROUGH;
|
||
case Format::R8G8B8_UNORM: FALLTHROUGH;
|
||
case Format::R8G8B8A8_UNORM: FALLTHROUGH;
|
||
case Format::R16_UNORM: FALLTHROUGH;
|
||
case Format::R16G16_UNORM:
|
||
return GL_TRUE;
|
||
default:
|
||
break;
|
||
}
|
||
return GL_FALSE;
|
||
}
|
||
|
||
int GetAttributeLocationFromStream(
|
||
CDevice* device, const VertexAttributeStream stream)
|
||
{
|
||
// Old mapping makes sense only if we have an old/low-end hardware. Else we
|
||
// need to use sequential numbering to fix #3054. We use presence of
|
||
// compute shaders as a check that the hardware has universal CUs.
|
||
if (device->GetCapabilities().computeShaders)
|
||
{
|
||
return static_cast<int>(stream);
|
||
}
|
||
else
|
||
{
|
||
// Map known semantics onto the attribute locations documented by NVIDIA:
|
||
// https://download.nvidia.com/developer/Papers/2005/OpenGL_2.0/NVIDIA_OpenGL_2.0_Support.pdf
|
||
// https://developer.download.nvidia.com/opengl/glsl/glsl_release_notes.pdf
|
||
switch (stream)
|
||
{
|
||
case VertexAttributeStream::POSITION: return 0;
|
||
case VertexAttributeStream::NORMAL: return 2;
|
||
case VertexAttributeStream::COLOR: return 3;
|
||
case VertexAttributeStream::UV0: return 8;
|
||
case VertexAttributeStream::UV1: return 9;
|
||
case VertexAttributeStream::UV2: return 10;
|
||
case VertexAttributeStream::UV3: return 11;
|
||
case VertexAttributeStream::UV4: return 12;
|
||
case VertexAttributeStream::UV5: return 13;
|
||
case VertexAttributeStream::UV6: return 14;
|
||
case VertexAttributeStream::UV7: return 15;
|
||
}
|
||
}
|
||
|
||
debug_warn("Invalid attribute semantics");
|
||
return 0;
|
||
}
|
||
|
||
bool PreprocessShaderFile(
|
||
bool arb, const CShaderDefines& defines, const VfsPath& path, const char* stage,
|
||
CStr& source, std::vector<VfsPath>& fileDependencies)
|
||
{
|
||
CVFSFile file;
|
||
if (file.Load(g_VFS, path) != PSRETURN_OK)
|
||
{
|
||
LOGERROR("Failed to load shader file: '%s'", path.string8());
|
||
return false;
|
||
}
|
||
|
||
CPreprocessorWrapper preprocessor(
|
||
[arb, &fileDependencies](const CStr& includePath, CStr& out) -> bool
|
||
{
|
||
const VfsPath includeFilePath(
|
||
(arb ? L"shaders/arb/" : L"shaders/glsl/") + wstring_from_utf8(includePath));
|
||
// Add dependencies anyway to reload the shader when the file is
|
||
// appeared.
|
||
fileDependencies.push_back(includeFilePath);
|
||
CVFSFile includeFile;
|
||
if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK)
|
||
{
|
||
LOGERROR("Failed to load shader include file: '%s'", includeFilePath.string8());
|
||
return false;
|
||
}
|
||
out = includeFile.GetAsString();
|
||
return true;
|
||
});
|
||
preprocessor.AddDefines(defines);
|
||
if (!arb)
|
||
preprocessor.AddDefine(stage, "1");
|
||
|
||
#if CONFIG2_GLES
|
||
if (!arb)
|
||
{
|
||
// GLES defines the macro "GL_ES" in its GLSL preprocessor,
|
||
// but since we run our own preprocessor first, we need to explicitly
|
||
// define it here
|
||
preprocessor.AddDefine("GL_ES", "1");
|
||
}
|
||
#endif
|
||
|
||
source = preprocessor.Preprocess(file.GetAsString());
|
||
|
||
return true;
|
||
}
|
||
|
||
#if !CONFIG2_GLES
|
||
std::tuple<GLenum, GLenum, GLint> GetElementTypeAndCountFromString(const CStr& str)
|
||
{
|
||
#define CASE(MATCH_STRING, TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \
|
||
if (str == MATCH_STRING) return {GL_ ## TYPE, GL_ ## ELEMENT_TYPE, ELEMENT_COUNT}
|
||
|
||
CASE("float", FLOAT, FLOAT, 1);
|
||
CASE("vec2", FLOAT_VEC2, FLOAT, 2);
|
||
CASE("vec3", FLOAT_VEC3, FLOAT, 3);
|
||
CASE("vec4", FLOAT_VEC4, FLOAT, 4);
|
||
CASE("mat2", FLOAT_MAT2, FLOAT, 4);
|
||
CASE("mat3", FLOAT_MAT3, FLOAT, 9);
|
||
CASE("mat4", FLOAT_MAT4, FLOAT, 16);
|
||
#if !CONFIG2_GLES // GL ES 2.0 doesn't support non-square matrices.
|
||
CASE("mat2x3", FLOAT_MAT2x3, FLOAT, 6);
|
||
CASE("mat2x4", FLOAT_MAT2x4, FLOAT, 8);
|
||
CASE("mat3x2", FLOAT_MAT3x2, FLOAT, 6);
|
||
CASE("mat3x4", FLOAT_MAT3x4, FLOAT, 12);
|
||
CASE("mat4x2", FLOAT_MAT4x2, FLOAT, 8);
|
||
CASE("mat4x3", FLOAT_MAT4x3, FLOAT, 12);
|
||
#endif
|
||
|
||
// A somewhat incomplete listing, missing "shadow" and "rect" versions
|
||
// which are interpreted as 2D (NB: our shadowmaps may change
|
||
// type based on user config).
|
||
#if CONFIG2_GLES
|
||
if (str == "sampler1D") debug_warn(L"sampler1D not implemented on GLES");
|
||
#else
|
||
CASE("sampler1D", SAMPLER_1D, TEXTURE_1D, 1);
|
||
#endif
|
||
CASE("sampler2D", SAMPLER_2D, TEXTURE_2D, 1);
|
||
#if CONFIG2_GLES
|
||
if (str == "sampler2DShadow") debug_warn(L"sampler2DShadow not implemented on GLES");
|
||
if (str == "sampler3D") debug_warn(L"sampler3D not implemented on GLES");
|
||
#else
|
||
CASE("sampler2DShadow", SAMPLER_2D_SHADOW, TEXTURE_2D, 1);
|
||
CASE("sampler3D", SAMPLER_3D, TEXTURE_3D, 1);
|
||
#endif
|
||
CASE("samplerCube", SAMPLER_CUBE, TEXTURE_CUBE_MAP, 1);
|
||
|
||
#undef CASE
|
||
return {0, 0, 0};
|
||
}
|
||
#endif // !CONFIG2_GLES
|
||
|
||
bool CompileGLSL(GLuint shader, const VfsPath& file, const CStr& code)
|
||
{
|
||
const char* codeString = code.c_str();
|
||
GLint codeLength = code.length();
|
||
glShaderSource(shader, 1, &codeString, &codeLength);
|
||
|
||
ogl_WarnIfError();
|
||
|
||
glCompileShader(shader);
|
||
|
||
GLint ok = 0;
|
||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||
|
||
GLint length = 0;
|
||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
|
||
|
||
// Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0
|
||
// (http://code.google.com/p/android/issues/detail?id=9953)
|
||
if (!ok && length == 0)
|
||
length = 4096;
|
||
|
||
if (length > 1)
|
||
{
|
||
std::unique_ptr<char[]> infolog = std::make_unique<char[]>(length);
|
||
glGetShaderInfoLog(shader, length, nullptr, infolog.get());
|
||
|
||
if (ok)
|
||
LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog.get());
|
||
else
|
||
LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog.get());
|
||
}
|
||
|
||
ogl_WarnIfError();
|
||
|
||
return ok;
|
||
}
|
||
|
||
} // anonymous namespace
|
||
|
||
IDevice* CVertexInputLayout::GetDevice()
|
||
{
|
||
return m_Device;
|
||
}
|
||
|
||
#if !CONFIG2_GLES
|
||
|
||
class CShaderProgramARB final : public CShaderProgram
|
||
{
|
||
public:
|
||
CShaderProgramARB(
|
||
CDevice* device,
|
||
const VfsPath& path, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath,
|
||
const CShaderDefines& defines,
|
||
const std::map<CStrIntern, std::pair<CStr, int>>& vertexIndices,
|
||
const std::map<CStrIntern, std::pair<CStr, int>>& fragmentIndices,
|
||
int streamflags)
|
||
: CShaderProgram(streamflags), m_Device(device)
|
||
{
|
||
glGenProgramsARB(1, &m_VertexProgram);
|
||
glGenProgramsARB(1, &m_FragmentProgram);
|
||
|
||
std::vector<VfsPath> newFileDependencies = {path, vertexFilePath, fragmentFilePath};
|
||
|
||
CStr vertexCode;
|
||
if (!PreprocessShaderFile(true, defines, vertexFilePath, "STAGE_VERTEX", vertexCode, newFileDependencies))
|
||
return;
|
||
CStr fragmentCode;
|
||
if (!PreprocessShaderFile(true, defines, fragmentFilePath, "STAGE_FRAGMENT", fragmentCode, newFileDependencies))
|
||
return;
|
||
|
||
m_FileDependencies = std::move(newFileDependencies);
|
||
|
||
// TODO: replace by scoped bind.
|
||
m_Device->GetActiveCommandContext()->SetGraphicsPipelineState(
|
||
MakeDefaultGraphicsPipelineStateDesc());
|
||
|
||
if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, vertexFilePath, vertexCode))
|
||
return;
|
||
|
||
if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, fragmentFilePath, fragmentCode))
|
||
return;
|
||
|
||
for (const auto& index : vertexIndices)
|
||
{
|
||
BindingSlot& bindingSlot = GetOrCreateBindingSlot(index.first);
|
||
bindingSlot.vertexProgramLocation = index.second.second;
|
||
const auto [type, elementType, elementCount] = GetElementTypeAndCountFromString(index.second.first);
|
||
bindingSlot.type = type;
|
||
bindingSlot.elementType = elementType;
|
||
bindingSlot.elementCount = elementCount;
|
||
}
|
||
|
||
for (const auto& index : fragmentIndices)
|
||
{
|
||
BindingSlot& bindingSlot = GetOrCreateBindingSlot(index.first);
|
||
bindingSlot.fragmentProgramLocation = index.second.second;
|
||
const auto [type, elementType, elementCount] = GetElementTypeAndCountFromString(index.second.first);
|
||
if (bindingSlot.type && type != bindingSlot.type)
|
||
{
|
||
LOGERROR("CShaderProgramARB: vertex and fragment program uniforms with the same name should have the same type.");
|
||
}
|
||
bindingSlot.type = type;
|
||
bindingSlot.elementType = elementType;
|
||
bindingSlot.elementCount = elementCount;
|
||
}
|
||
}
|
||
|
||
~CShaderProgramARB() override
|
||
{
|
||
glDeleteProgramsARB(1, &m_VertexProgram);
|
||
glDeleteProgramsARB(1, &m_FragmentProgram);
|
||
}
|
||
|
||
bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code)
|
||
{
|
||
ogl_WarnIfError();
|
||
|
||
glBindProgramARB(target, program);
|
||
|
||
ogl_WarnIfError();
|
||
|
||
glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str());
|
||
|
||
if (ogl_SquelchError(GL_INVALID_OPERATION))
|
||
{
|
||
GLint errPos = 0;
|
||
glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos);
|
||
int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1;
|
||
char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
|
||
LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr);
|
||
return false;
|
||
}
|
||
|
||
glBindProgramARB(target, 0);
|
||
|
||
ogl_WarnIfError();
|
||
|
||
return true;
|
||
}
|
||
|
||
void Bind(CShaderProgram* previousShaderProgram) override
|
||
{
|
||
if (previousShaderProgram)
|
||
previousShaderProgram->Unbind();
|
||
|
||
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram);
|
||
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram);
|
||
|
||
BindClientStates();
|
||
}
|
||
|
||
void Unbind() override
|
||
{
|
||
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
|
||
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
|
||
|
||
UnbindClientStates();
|
||
}
|
||
|
||
IDevice* GetDevice() override { return m_Device; }
|
||
|
||
int32_t GetBindingSlot(const CStrIntern name) const override
|
||
{
|
||
auto it = m_BindingSlotsMapping.find(name);
|
||
return it == m_BindingSlotsMapping.end() ? -1 : it->second;
|
||
}
|
||
|
||
TextureUnit GetTextureUnit(const int32_t bindingSlot) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return { 0, 0, 0 };
|
||
TextureUnit textureUnit;
|
||
textureUnit.type = m_BindingSlots[bindingSlot].type;
|
||
textureUnit.target = m_BindingSlots[bindingSlot].elementType;
|
||
textureUnit.unit = m_BindingSlots[bindingSlot].fragmentProgramLocation;
|
||
return textureUnit;
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float value) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT)
|
||
{
|
||
LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected float)");
|
||
return;
|
||
}
|
||
SetUniform(m_BindingSlots[bindingSlot], value, 0.0f, 0.0f, 0.0f);
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float valueX, const float valueY) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC2)
|
||
{
|
||
LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec2)");
|
||
return;
|
||
}
|
||
SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, 0.0f, 0.0f);
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float valueX, const float valueY, const float valueZ) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC3)
|
||
{
|
||
LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec3)");
|
||
return;
|
||
}
|
||
SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, valueZ, 0.0f);
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float valueX, const float valueY,
|
||
const float valueZ, const float valueW) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC4)
|
||
{
|
||
LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected vec4)");
|
||
return;
|
||
}
|
||
SetUniform(m_BindingSlots[bindingSlot], valueX, valueY, valueZ, valueW);
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot, PS::span<const float> values) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT)
|
||
{
|
||
LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform element type (expected float)");
|
||
return;
|
||
}
|
||
if (m_BindingSlots[bindingSlot].elementCount > static_cast<GLint>(values.size()))
|
||
{
|
||
LOGERROR(
|
||
"CShaderProgramARB::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu)",
|
||
m_BindingSlots[bindingSlot].elementCount, values.size());
|
||
return;
|
||
}
|
||
const GLenum type = m_BindingSlots[bindingSlot].type;
|
||
|
||
if (type == GL_FLOAT)
|
||
SetUniform(m_BindingSlots[bindingSlot], values[0], 0.0f, 0.0f, 0.0f);
|
||
else if (type == GL_FLOAT_VEC2)
|
||
SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], 0.0f, 0.0f);
|
||
else if (type == GL_FLOAT_VEC3)
|
||
SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], values[2], 0.0f);
|
||
else if (type == GL_FLOAT_VEC4)
|
||
SetUniform(m_BindingSlots[bindingSlot], values[0], values[1], values[2], values[3]);
|
||
else if (type == GL_FLOAT_MAT4)
|
||
SetUniformMatrix(m_BindingSlots[bindingSlot], values);
|
||
else
|
||
LOGERROR("CShaderProgramARB::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4)");
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
std::vector<VfsPath> GetFileDependencies() const override
|
||
{
|
||
return m_FileDependencies;
|
||
}
|
||
|
||
private:
|
||
struct BindingSlot
|
||
{
|
||
CStrIntern name;
|
||
int vertexProgramLocation;
|
||
int fragmentProgramLocation;
|
||
GLenum type;
|
||
GLenum elementType;
|
||
GLint elementCount;
|
||
};
|
||
|
||
BindingSlot& GetOrCreateBindingSlot(const CStrIntern name)
|
||
{
|
||
auto it = m_BindingSlotsMapping.find(name);
|
||
if (it == m_BindingSlotsMapping.end())
|
||
{
|
||
m_BindingSlotsMapping[name] = m_BindingSlots.size();
|
||
BindingSlot bindingSlot{};
|
||
bindingSlot.name = name;
|
||
bindingSlot.vertexProgramLocation = -1;
|
||
bindingSlot.fragmentProgramLocation = -1;
|
||
bindingSlot.elementType = 0;
|
||
bindingSlot.elementCount = 0;
|
||
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
||
return m_BindingSlots.back();
|
||
}
|
||
else
|
||
return m_BindingSlots[it->second];
|
||
}
|
||
|
||
void SetUniform(
|
||
const BindingSlot& bindingSlot,
|
||
const float v0, const float v1, const float v2, const float v3)
|
||
{
|
||
SetUniform(GL_VERTEX_PROGRAM_ARB, bindingSlot.vertexProgramLocation, v0, v1, v2, v3);
|
||
SetUniform(GL_FRAGMENT_PROGRAM_ARB, bindingSlot.fragmentProgramLocation, v0, v1, v2, v3);
|
||
}
|
||
|
||
void SetUniform(
|
||
const GLenum target, const int location,
|
||
const float v0, const float v1, const float v2, const float v3)
|
||
{
|
||
if (location >= 0)
|
||
{
|
||
glProgramLocalParameter4fARB(
|
||
target, static_cast<GLuint>(location), v0, v1, v2, v3);
|
||
}
|
||
}
|
||
|
||
void SetUniformMatrix(
|
||
const BindingSlot& bindingSlot, PS::span<const float> values)
|
||
{
|
||
const size_t mat4ElementCount = 16;
|
||
ENSURE(values.size() == mat4ElementCount);
|
||
SetUniformMatrix(GL_VERTEX_PROGRAM_ARB, bindingSlot.vertexProgramLocation, values);
|
||
SetUniformMatrix(GL_FRAGMENT_PROGRAM_ARB, bindingSlot.fragmentProgramLocation, values);
|
||
}
|
||
|
||
void SetUniformMatrix(
|
||
const GLenum target, const int location, PS::span<const float> values)
|
||
{
|
||
if (location >= 0)
|
||
{
|
||
glProgramLocalParameter4fARB(
|
||
target, static_cast<GLuint>(location + 0), values[0], values[4], values[8], values[12]);
|
||
glProgramLocalParameter4fARB(
|
||
target, static_cast<GLuint>(location + 1), values[1], values[5], values[9], values[13]);
|
||
glProgramLocalParameter4fARB(
|
||
target, static_cast<GLuint>(location + 2), values[2], values[6], values[10], values[14]);
|
||
glProgramLocalParameter4fARB(
|
||
target, static_cast<GLuint>(location + 3), values[3], values[7], values[11], values[15]);
|
||
}
|
||
}
|
||
|
||
CDevice* m_Device = nullptr;
|
||
|
||
std::vector<VfsPath> m_FileDependencies;
|
||
|
||
GLuint m_VertexProgram;
|
||
GLuint m_FragmentProgram;
|
||
|
||
std::vector<BindingSlot> m_BindingSlots;
|
||
std::unordered_map<CStrIntern, int32_t> m_BindingSlotsMapping;
|
||
};
|
||
|
||
#endif // !CONFIG2_GLES
|
||
|
||
class CShaderProgramGLSL final : public CShaderProgram
|
||
{
|
||
public:
|
||
CShaderProgramGLSL(
|
||
CDevice* device, const CStr& name,
|
||
const VfsPath& programPath, PS::span<const std::tuple<VfsPath, GLenum>> shaderStages,
|
||
const CShaderDefines& defines,
|
||
const std::map<CStrIntern, int>& vertexAttribs,
|
||
int streamflags) :
|
||
CShaderProgram(streamflags),
|
||
m_Device(device), m_Name(name),
|
||
m_VertexAttribs(vertexAttribs)
|
||
{
|
||
for (std::map<CStrIntern, int>::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
|
||
m_ActiveVertexAttributes.emplace_back(it->second);
|
||
std::sort(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end());
|
||
|
||
m_Program = 0;
|
||
m_FileDependencies = {programPath};
|
||
for (const auto& [path, type] : shaderStages)
|
||
m_FileDependencies.emplace_back(path);
|
||
|
||
// TODO: replace by scoped bind.
|
||
m_Device->GetActiveCommandContext()->SetGraphicsPipelineState(
|
||
MakeDefaultGraphicsPipelineStateDesc());
|
||
|
||
std::vector<VfsPath> newFileDependencies = {programPath};
|
||
for (const auto& [path, type] : shaderStages)
|
||
{
|
||
GLuint shader = glCreateShader(type);
|
||
newFileDependencies.emplace_back(path);
|
||
#if !CONFIG2_GLES
|
||
if (m_Device->GetCapabilities().debugLabels)
|
||
glObjectLabel(GL_SHADER, shader, -1, path.string8().c_str());
|
||
#endif
|
||
m_ShaderStages.emplace_back(type, shader);
|
||
const char* stageDefine = "STAGE_UNDEFINED";
|
||
switch (type)
|
||
{
|
||
case GL_VERTEX_SHADER:
|
||
stageDefine = "STAGE_VERTEX";
|
||
break;
|
||
case GL_FRAGMENT_SHADER:
|
||
stageDefine = "STAGE_FRAGMENT";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
CStr source;
|
||
if (!PreprocessShaderFile(false, defines, path, stageDefine, source, newFileDependencies))
|
||
return;
|
||
if (source.empty())
|
||
{
|
||
LOGERROR("Failed to preprocess shader: '%s'", path.string8());
|
||
return;
|
||
}
|
||
#if CONFIG2_GLES
|
||
// Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00,
|
||
// and also to set default float precision for fragment shaders
|
||
source.Replace("#version 110\n", "#version 100\nprecision highp float;\n");
|
||
source.Replace("#version 110\r\n", "#version 100\nprecision highp float;\n");
|
||
source.Replace("#version 120\n", "#version 100\nprecision highp float;\n");
|
||
source.Replace("#version 120\r\n", "#version 100\nprecision highp float;\n");
|
||
#endif
|
||
if (!CompileGLSL(shader, path, source))
|
||
return;
|
||
}
|
||
|
||
m_FileDependencies = std::move(newFileDependencies);
|
||
|
||
if (!Link(programPath))
|
||
return;
|
||
}
|
||
|
||
~CShaderProgramGLSL() override
|
||
{
|
||
if (m_Program)
|
||
glDeleteProgram(m_Program);
|
||
|
||
for (ShaderStage& stage : m_ShaderStages)
|
||
glDeleteShader(stage.shader);
|
||
}
|
||
|
||
bool Link(const VfsPath& path)
|
||
{
|
||
ENSURE(!m_Program);
|
||
m_Program = glCreateProgram();
|
||
|
||
#if !CONFIG2_GLES
|
||
if (m_Device->GetCapabilities().debugLabels)
|
||
{
|
||
glObjectLabel(GL_PROGRAM, m_Program, -1, m_Name.c_str());
|
||
}
|
||
#endif
|
||
|
||
for (ShaderStage& stage : m_ShaderStages)
|
||
{
|
||
glAttachShader(m_Program, stage.shader);
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
// Set up the attribute bindings explicitly, since apparently drivers
|
||
// don't always pick the most efficient bindings automatically,
|
||
// and also this lets us hardcode indexes into VertexPointer etc
|
||
for (std::map<CStrIntern, int>::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
|
||
glBindAttribLocation(m_Program, it->second, it->first.c_str());
|
||
|
||
glLinkProgram(m_Program);
|
||
|
||
GLint ok = 0;
|
||
glGetProgramiv(m_Program, GL_LINK_STATUS, &ok);
|
||
|
||
GLint length = 0;
|
||
glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length);
|
||
|
||
if (!ok && length == 0)
|
||
length = 4096;
|
||
|
||
if (length > 1)
|
||
{
|
||
char* infolog = new char[length];
|
||
glGetProgramInfoLog(m_Program, length, NULL, infolog);
|
||
|
||
if (ok)
|
||
LOGMESSAGE("Info when linking program '%s':\n%s", path.string8(), infolog);
|
||
else
|
||
LOGERROR("Failed to link program '%s':\n%s", path.string8(), infolog);
|
||
|
||
delete[] infolog;
|
||
}
|
||
|
||
ogl_WarnIfError();
|
||
|
||
if (!ok)
|
||
return false;
|
||
|
||
Bind(nullptr);
|
||
|
||
ogl_WarnIfError();
|
||
|
||
// Reorder sampler units to decrease redundant texture unit changes when
|
||
// samplers bound in a different order.
|
||
const std::unordered_map<CStrIntern, int> requiredUnits =
|
||
{
|
||
{CStrIntern("baseTex"), 0},
|
||
{CStrIntern("normTex"), 1},
|
||
{CStrIntern("specTex"), 2},
|
||
{CStrIntern("aoTex"), 3},
|
||
{CStrIntern("shadowTex"), 4},
|
||
{CStrIntern("losTex"), 5},
|
||
};
|
||
|
||
std::vector<uint8_t> occupiedUnits;
|
||
|
||
GLint numUniforms = 0;
|
||
glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms);
|
||
ogl_WarnIfError();
|
||
for (GLint i = 0; i < numUniforms; ++i)
|
||
{
|
||
// TODO: use GL_ACTIVE_UNIFORM_MAX_LENGTH for the size.
|
||
char name[256] = {0};
|
||
GLsizei nameLength = 0;
|
||
GLint size = 0;
|
||
GLenum type = 0;
|
||
glGetActiveUniform(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name);
|
||
ogl_WarnIfError();
|
||
|
||
const GLint location = glGetUniformLocation(m_Program, name);
|
||
|
||
// OpenGL specification is a bit vague about a name returned by glGetActiveUniform.
|
||
// NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without;
|
||
while (nameLength > 3 &&
|
||
name[nameLength - 3] == '[' &&
|
||
name[nameLength - 2] == '0' &&
|
||
name[nameLength - 1] == ']')
|
||
{
|
||
nameLength -= 3;
|
||
}
|
||
name[nameLength] = 0;
|
||
|
||
const CStrIntern nameIntern(name);
|
||
|
||
m_BindingSlotsMapping[nameIntern] = m_BindingSlots.size();
|
||
BindingSlot bindingSlot{};
|
||
bindingSlot.name = nameIntern;
|
||
bindingSlot.location = location;
|
||
bindingSlot.size = size;
|
||
bindingSlot.type = type;
|
||
bindingSlot.isTexture = false;
|
||
|
||
#define CASE(TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \
|
||
case GL_ ## TYPE: \
|
||
bindingSlot.elementType = GL_ ## ELEMENT_TYPE; \
|
||
bindingSlot.elementCount = ELEMENT_COUNT; \
|
||
break;
|
||
|
||
switch (type)
|
||
{
|
||
CASE(FLOAT, FLOAT, 1);
|
||
CASE(FLOAT_VEC2, FLOAT, 2);
|
||
CASE(FLOAT_VEC3, FLOAT, 3);
|
||
CASE(FLOAT_VEC4, FLOAT, 4);
|
||
CASE(INT, INT, 1);
|
||
CASE(FLOAT_MAT2, FLOAT, 4);
|
||
CASE(FLOAT_MAT3, FLOAT, 9);
|
||
CASE(FLOAT_MAT4, FLOAT, 16);
|
||
#if !CONFIG2_GLES // GL ES 2.0 doesn't support non-square matrices.
|
||
CASE(FLOAT_MAT2x3, FLOAT, 6);
|
||
CASE(FLOAT_MAT2x4, FLOAT, 8);
|
||
CASE(FLOAT_MAT3x2, FLOAT, 6);
|
||
CASE(FLOAT_MAT3x4, FLOAT, 12);
|
||
CASE(FLOAT_MAT4x2, FLOAT, 8);
|
||
CASE(FLOAT_MAT4x3, FLOAT, 12);
|
||
#endif
|
||
}
|
||
#undef CASE
|
||
|
||
// Assign sampler uniforms to sequential texture units.
|
||
if (type == GL_SAMPLER_2D
|
||
|| type == GL_SAMPLER_CUBE
|
||
#if !CONFIG2_GLES
|
||
|| type == GL_SAMPLER_2D_SHADOW
|
||
#endif
|
||
)
|
||
{
|
||
const auto it = requiredUnits.find(nameIntern);
|
||
const int unit = it == requiredUnits.end() ? -1 : it->second;
|
||
bindingSlot.elementType = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D);
|
||
bindingSlot.elementCount = unit;
|
||
bindingSlot.isTexture = true;
|
||
if (unit != -1)
|
||
{
|
||
if (unit >= static_cast<int>(occupiedUnits.size()))
|
||
occupiedUnits.resize(unit + 1);
|
||
occupiedUnits[unit] = true;
|
||
}
|
||
}
|
||
|
||
if (bindingSlot.elementType == 0)
|
||
{
|
||
LOGERROR("CShaderProgramGLSL::Link: unsupported uniform type: 0x%04x", static_cast<int>(type));
|
||
}
|
||
|
||
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
||
}
|
||
|
||
for (BindingSlot& bindingSlot : m_BindingSlots)
|
||
{
|
||
if (!bindingSlot.isTexture)
|
||
continue;
|
||
if (bindingSlot.elementCount == -1)
|
||
{
|
||
// We need to find a minimal available unit.
|
||
int unit = 0;
|
||
while (unit < static_cast<int>(occupiedUnits.size()) && occupiedUnits[unit])
|
||
++unit;
|
||
if (unit >= static_cast<int>(occupiedUnits.size()))
|
||
occupiedUnits.resize(unit + 1);
|
||
occupiedUnits[unit] = true;
|
||
bindingSlot.elementCount = unit;
|
||
}
|
||
// Link uniform to unit.
|
||
glUniform1i(bindingSlot.location, bindingSlot.elementCount);
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
// TODO: verify that we're not using more samplers than is supported
|
||
|
||
Unbind();
|
||
|
||
return true;
|
||
}
|
||
|
||
void Bind(CShaderProgram* previousShaderProgram) override
|
||
{
|
||
CShaderProgramGLSL* previousShaderProgramGLSL = nullptr;
|
||
if (previousShaderProgram)
|
||
previousShaderProgramGLSL = static_cast<CShaderProgramGLSL*>(previousShaderProgram);
|
||
ENSURE(this != previousShaderProgramGLSL);
|
||
|
||
glUseProgram(m_Program);
|
||
|
||
if (previousShaderProgramGLSL)
|
||
{
|
||
std::vector<int>::iterator itPrevious = previousShaderProgramGLSL->m_ActiveVertexAttributes.begin();
|
||
std::vector<int>::iterator itNext = m_ActiveVertexAttributes.begin();
|
||
while (
|
||
itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() ||
|
||
itNext != m_ActiveVertexAttributes.end())
|
||
{
|
||
if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() &&
|
||
itNext != m_ActiveVertexAttributes.end())
|
||
{
|
||
if (*itPrevious == *itNext)
|
||
{
|
||
++itPrevious;
|
||
++itNext;
|
||
}
|
||
else if (*itPrevious < *itNext)
|
||
{
|
||
glDisableVertexAttribArray(*itPrevious);
|
||
++itPrevious;
|
||
}
|
||
else if (*itPrevious > *itNext)
|
||
{
|
||
glEnableVertexAttribArray(*itNext);
|
||
++itNext;
|
||
}
|
||
}
|
||
else if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end())
|
||
{
|
||
glDisableVertexAttribArray(*itPrevious);
|
||
++itPrevious;
|
||
}
|
||
else if (itNext != m_ActiveVertexAttributes.end())
|
||
{
|
||
glEnableVertexAttribArray(*itNext);
|
||
++itNext;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (const int index : m_ActiveVertexAttributes)
|
||
glEnableVertexAttribArray(index);
|
||
}
|
||
|
||
m_ValidStreams = 0;
|
||
}
|
||
|
||
void Unbind() override
|
||
{
|
||
glUseProgram(0);
|
||
|
||
for (const int index : m_ActiveVertexAttributes)
|
||
glDisableVertexAttribArray(index);
|
||
}
|
||
|
||
IDevice* GetDevice() override { return m_Device; }
|
||
|
||
int32_t GetBindingSlot(const CStrIntern name) const override
|
||
{
|
||
auto it = m_BindingSlotsMapping.find(name);
|
||
return it == m_BindingSlotsMapping.end() ? -1 : it->second;
|
||
}
|
||
|
||
TextureUnit GetTextureUnit(const int32_t bindingSlot) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return { 0, 0, 0 };
|
||
TextureUnit textureUnit;
|
||
textureUnit.type = m_BindingSlots[bindingSlot].type;
|
||
textureUnit.target = m_BindingSlots[bindingSlot].elementType;
|
||
textureUnit.unit = m_BindingSlots[bindingSlot].elementCount;
|
||
return textureUnit;
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float value) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT ||
|
||
m_BindingSlots[bindingSlot].size != 1)
|
||
{
|
||
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||
return;
|
||
}
|
||
glUniform1f(m_BindingSlots[bindingSlot].location, value);
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float valueX, const float valueY) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC2 ||
|
||
m_BindingSlots[bindingSlot].size != 1)
|
||
{
|
||
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec2) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||
return;
|
||
}
|
||
glUniform2f(m_BindingSlots[bindingSlot].location, valueX, valueY);
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float valueX, const float valueY, const float valueZ) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC3 ||
|
||
m_BindingSlots[bindingSlot].size != 1)
|
||
{
|
||
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec3) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||
return;
|
||
}
|
||
glUniform3f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ);
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot,
|
||
const float valueX, const float valueY,
|
||
const float valueZ, const float valueW) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].type != GL_FLOAT_VEC4 ||
|
||
m_BindingSlots[bindingSlot].size != 1)
|
||
{
|
||
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec4) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||
return;
|
||
}
|
||
glUniform4f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ, valueW);
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
void SetUniform(
|
||
const int32_t bindingSlot, PS::span<const float> values) override
|
||
{
|
||
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
||
return;
|
||
if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT)
|
||
{
|
||
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform element type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||
return;
|
||
}
|
||
if (m_BindingSlots[bindingSlot].size == 1 && m_BindingSlots[bindingSlot].elementCount > static_cast<GLint>(values.size()))
|
||
{
|
||
LOGERROR(
|
||
"CShaderProgramGLSL::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu) '%s'",
|
||
m_BindingSlots[bindingSlot].elementCount, values.size(), m_BindingSlots[bindingSlot].name.c_str());
|
||
return;
|
||
}
|
||
const GLint location = m_BindingSlots[bindingSlot].location;
|
||
const GLenum type = m_BindingSlots[bindingSlot].type;
|
||
|
||
if (type == GL_FLOAT)
|
||
glUniform1fv(location, 1, values.data());
|
||
else if (type == GL_FLOAT_VEC2)
|
||
glUniform2fv(location, 1, values.data());
|
||
else if (type == GL_FLOAT_VEC3)
|
||
glUniform3fv(location, 1, values.data());
|
||
else if (type == GL_FLOAT_VEC4)
|
||
glUniform4fv(location, 1, values.data());
|
||
else if (type == GL_FLOAT_MAT4)
|
||
{
|
||
// For case of array of matrices we might pass less number of matrices.
|
||
const GLint size = std::min(
|
||
m_BindingSlots[bindingSlot].size, static_cast<GLint>(values.size() / 16));
|
||
glUniformMatrix4fv(location, size, GL_FALSE, values.data());
|
||
}
|
||
else
|
||
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
||
ogl_WarnIfError();
|
||
}
|
||
|
||
void VertexAttribPointer(
|
||
const VertexAttributeStream stream, const Format format,
|
||
const uint32_t offset, const uint32_t stride,
|
||
const VertexAttributeRate rate, const void* data) override
|
||
{
|
||
const int attributeLocation = GetAttributeLocationFromStream(m_Device, stream);
|
||
std::vector<int>::const_iterator it =
|
||
std::lower_bound(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end(), attributeLocation);
|
||
if (it == m_ActiveVertexAttributes.end() || *it != attributeLocation)
|
||
return;
|
||
const GLint size = GLSizeFromFormat(format);
|
||
const GLenum type = GLTypeFromFormat(format);
|
||
const GLboolean normalized = NormalizedFromFormat(format);
|
||
glVertexAttribPointer(
|
||
attributeLocation, size, type, normalized, stride, static_cast<const u8*>(data) + offset);
|
||
#if CONFIG2_GLES
|
||
ENSURE(!m_Device->GetCapabilities().instancing);
|
||
UNUSED2(rate);
|
||
#else
|
||
if (rate == VertexAttributeRate::PER_INSTANCE)
|
||
ENSURE(m_Device->GetCapabilities().instancing);
|
||
if (m_Device->GetCapabilities().instancing)
|
||
{
|
||
glVertexAttribDivisorARB(attributeLocation, rate == VertexAttributeRate::PER_INSTANCE ? 1 : 0);
|
||
}
|
||
#endif
|
||
m_ValidStreams |= GetStreamMask(stream);
|
||
}
|
||
|
||
std::vector<VfsPath> GetFileDependencies() const override
|
||
{
|
||
return m_FileDependencies;
|
||
}
|
||
|
||
private:
|
||
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 size;
|
||
GLenum type;
|
||
GLenum elementType;
|
||
GLint elementCount;
|
||
bool isTexture;
|
||
};
|
||
std::vector<BindingSlot> m_BindingSlots;
|
||
std::unordered_map<CStrIntern, int32_t> m_BindingSlotsMapping;
|
||
};
|
||
|
||
CShaderProgram::CShaderProgram(int streamflags)
|
||
: m_StreamFlags(streamflags), m_ValidStreams(0)
|
||
{
|
||
}
|
||
|
||
CShaderProgram::~CShaderProgram() = default;
|
||
|
||
// static
|
||
std::unique_ptr<CShaderProgram> CShaderProgram::Create(CDevice* device, const CStr& name, const CShaderDefines& baseDefines)
|
||
{
|
||
PROFILE2("loading shader");
|
||
PROFILE2_ATTR("name: %s", name.c_str());
|
||
|
||
VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml";
|
||
|
||
CXeromyces XeroFile;
|
||
PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename);
|
||
if (ret != PSRETURN_OK)
|
||
return nullptr;
|
||
|
||
#if USE_SHADER_XML_VALIDATION
|
||
{
|
||
// Serialize the XMB data and pass it to the validator
|
||
XMLWriter_File shaderFile;
|
||
shaderFile.SetPrettyPrint(false);
|
||
shaderFile.XMB(XeroFile);
|
||
bool ok = CXeromyces::ValidateEncoded("shader", name, shaderFile.GetOutput());
|
||
if (!ok)
|
||
return nullptr;
|
||
}
|
||
#endif
|
||
|
||
// Define all the elements and attributes used in the XML file
|
||
#define EL(x) int el_##x = XeroFile.GetElementID(#x)
|
||
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
|
||
EL(define);
|
||
EL(fragment);
|
||
EL(stream);
|
||
EL(uniform);
|
||
EL(vertex);
|
||
AT(attribute);
|
||
AT(file);
|
||
AT(if);
|
||
AT(loc);
|
||
AT(name);
|
||
AT(type);
|
||
AT(value);
|
||
#undef AT
|
||
#undef EL
|
||
|
||
CPreprocessorWrapper preprocessor;
|
||
preprocessor.AddDefines(baseDefines);
|
||
|
||
XMBElement root = XeroFile.GetRoot();
|
||
|
||
const bool isGLSL = root.GetAttributes().GetNamedItem(at_type) == "glsl";
|
||
|
||
VfsPath vertexFile;
|
||
VfsPath fragmentFile;
|
||
CShaderDefines defines = baseDefines;
|
||
std::map<CStrIntern, std::pair<CStr, int>> vertexUniforms;
|
||
std::map<CStrIntern, std::pair<CStr, int>> fragmentUniforms;
|
||
std::map<CStrIntern, int> vertexAttribs;
|
||
int streamFlags = 0;
|
||
|
||
XERO_ITER_EL(root, child)
|
||
{
|
||
if (child.GetNodeName() == el_define)
|
||
{
|
||
defines.Add(CStrIntern(child.GetAttributes().GetNamedItem(at_name)), CStrIntern(child.GetAttributes().GetNamedItem(at_value)));
|
||
}
|
||
else if (child.GetNodeName() == el_vertex)
|
||
{
|
||
vertexFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8();
|
||
|
||
XERO_ITER_EL(child, param)
|
||
{
|
||
XMBAttributeList attributes = param.GetAttributes();
|
||
|
||
CStr cond = attributes.GetNamedItem(at_if);
|
||
if (!cond.empty() && !preprocessor.TestConditional(cond))
|
||
continue;
|
||
|
||
if (param.GetNodeName() == el_uniform)
|
||
{
|
||
vertexUniforms[CStrIntern(attributes.GetNamedItem(at_name))] =
|
||
std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt());
|
||
}
|
||
else if (param.GetNodeName() == el_stream)
|
||
{
|
||
const CStr streamName = attributes.GetNamedItem(at_name);
|
||
const CStr attributeName = attributes.GetNamedItem(at_attribute);
|
||
if (attributeName.empty() && isGLSL)
|
||
LOGERROR("Empty attribute name in vertex shader description '%s'", vertexFile.string8().c_str());
|
||
|
||
VertexAttributeStream stream =
|
||
VertexAttributeStream::UV7;
|
||
if (streamName == "pos")
|
||
stream = VertexAttributeStream::POSITION;
|
||
else if (streamName == "normal")
|
||
stream = VertexAttributeStream::NORMAL;
|
||
else if (streamName == "color")
|
||
stream = VertexAttributeStream::COLOR;
|
||
else if (streamName == "uv0")
|
||
stream = VertexAttributeStream::UV0;
|
||
else if (streamName == "uv1")
|
||
stream = VertexAttributeStream::UV1;
|
||
else if (streamName == "uv2")
|
||
stream = VertexAttributeStream::UV2;
|
||
else if (streamName == "uv3")
|
||
stream = VertexAttributeStream::UV3;
|
||
else if (streamName == "uv4")
|
||
stream = VertexAttributeStream::UV4;
|
||
else if (streamName == "uv5")
|
||
stream = VertexAttributeStream::UV5;
|
||
else if (streamName == "uv6")
|
||
stream = VertexAttributeStream::UV6;
|
||
else if (streamName == "uv7")
|
||
stream = VertexAttributeStream::UV7;
|
||
else
|
||
LOGERROR("Unknown stream '%s' in vertex shader description '%s'", streamName.c_str(), vertexFile.string8().c_str());
|
||
|
||
if (isGLSL)
|
||
{
|
||
const int attributeLocation = GetAttributeLocationFromStream(device, stream);
|
||
vertexAttribs[CStrIntern(attributeName)] = attributeLocation;
|
||
}
|
||
streamFlags |= GetStreamMask(stream);
|
||
}
|
||
}
|
||
}
|
||
else if (child.GetNodeName() == el_fragment)
|
||
{
|
||
fragmentFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8();
|
||
|
||
XERO_ITER_EL(child, param)
|
||
{
|
||
XMBAttributeList attributes = param.GetAttributes();
|
||
|
||
CStr cond = attributes.GetNamedItem(at_if);
|
||
if (!cond.empty() && !preprocessor.TestConditional(cond))
|
||
continue;
|
||
|
||
if (param.GetNodeName() == el_uniform)
|
||
{
|
||
fragmentUniforms[CStrIntern(attributes.GetNamedItem(at_name))] =
|
||
std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (isGLSL)
|
||
{
|
||
const std::array<std::tuple<VfsPath, GLenum>, 2> shaderStages{{
|
||
{vertexFile, GL_VERTEX_SHADER}, {fragmentFile, GL_FRAGMENT_SHADER}}};
|
||
return std::make_unique<CShaderProgramGLSL>(
|
||
device, name, xmlFilename, shaderStages, defines,
|
||
vertexAttribs, streamFlags);
|
||
}
|
||
else
|
||
{
|
||
#if CONFIG2_GLES
|
||
LOGERROR("CShaderProgram::Create: '%s'+'%s': ARB shaders not supported on this device",
|
||
vertexFile.string8(), fragmentFile.string8());
|
||
return nullptr;
|
||
#else
|
||
return std::make_unique<CShaderProgramARB>(
|
||
device, xmlFilename, vertexFile, fragmentFile, defines,
|
||
vertexUniforms, fragmentUniforms, streamFlags);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
// These should all be overridden by CShaderProgramGLSL, and not used
|
||
// if a non-GLSL shader was loaded instead:
|
||
|
||
#if CONFIG2_GLES
|
||
|
||
// These should all be overridden by CShaderProgramGLSL
|
||
// (GLES doesn't support any other types of shader program):
|
||
|
||
void CShaderProgram::VertexPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer))
|
||
{
|
||
debug_warn("CShaderProgram::VertexPointer should be overridden");
|
||
}
|
||
void CShaderProgram::NormalPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer))
|
||
{
|
||
debug_warn("CShaderProgram::NormalPointer should be overridden");
|
||
}
|
||
void CShaderProgram::ColorPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer))
|
||
{
|
||
debug_warn("CShaderProgram::ColorPointer should be overridden");
|
||
}
|
||
void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer))
|
||
{
|
||
debug_warn("CShaderProgram::TexCoordPointer should be overridden");
|
||
}
|
||
|
||
#else
|
||
|
||
// These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders
|
||
// both use the fixed-function vertex attribute pointers so we'll share their
|
||
// definitions here:
|
||
|
||
void CShaderProgram::VertexPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer)
|
||
{
|
||
const GLint size = GLSizeFromFormat(format);
|
||
ENSURE(2 <= size && size <= 4);
|
||
const GLenum type = GLTypeFromFormat(format);
|
||
glVertexPointer(size, type, stride, pointer);
|
||
m_ValidStreams |= GetStreamMask(VertexAttributeStream::POSITION);
|
||
}
|
||
|
||
void CShaderProgram::NormalPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer)
|
||
{
|
||
ENSURE(format == Renderer::Backend::Format::R32G32B32_SFLOAT);
|
||
glNormalPointer(GL_FLOAT, stride, pointer);
|
||
m_ValidStreams |= GetStreamMask(VertexAttributeStream::NORMAL);
|
||
}
|
||
|
||
void CShaderProgram::ColorPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer)
|
||
{
|
||
const GLint size = GLSizeFromFormat(format);
|
||
ENSURE(3 <= size && size <= 4);
|
||
const GLenum type = GLTypeFromFormat(format);
|
||
glColorPointer(size, type, stride, pointer);
|
||
m_ValidStreams |= GetStreamMask(VertexAttributeStream::COLOR);
|
||
}
|
||
|
||
void CShaderProgram::TexCoordPointer(GLenum texture, const Renderer::Backend::Format format, GLsizei stride, const void* pointer)
|
||
{
|
||
glClientActiveTextureARB(texture);
|
||
const GLint size = GLSizeFromFormat(format);
|
||
ENSURE(1 <= size && size <= 4);
|
||
const GLenum type = GLTypeFromFormat(format);
|
||
glTexCoordPointer(size, type, stride, pointer);
|
||
glClientActiveTextureARB(GL_TEXTURE0);
|
||
m_ValidStreams |= GetStreamMask(VertexAttributeStream::UV0) << (texture - GL_TEXTURE0);
|
||
}
|
||
|
||
void CShaderProgram::BindClientStates()
|
||
{
|
||
ENSURE(m_StreamFlags == (m_StreamFlags & (
|
||
GetStreamMask(VertexAttributeStream::POSITION) |
|
||
GetStreamMask(VertexAttributeStream::NORMAL) |
|
||
GetStreamMask(VertexAttributeStream::COLOR) |
|
||
GetStreamMask(VertexAttributeStream::UV0) |
|
||
GetStreamMask(VertexAttributeStream::UV1))));
|
||
|
||
// Enable all the desired client states for non-GLSL rendering
|
||
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION))
|
||
glEnableClientState(GL_VERTEX_ARRAY);
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL))
|
||
glEnableClientState(GL_NORMAL_ARRAY);
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR))
|
||
glEnableClientState(GL_COLOR_ARRAY);
|
||
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0))
|
||
{
|
||
glClientActiveTextureARB(GL_TEXTURE0);
|
||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||
}
|
||
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1))
|
||
{
|
||
glClientActiveTextureARB(GL_TEXTURE1);
|
||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||
glClientActiveTextureARB(GL_TEXTURE0);
|
||
}
|
||
|
||
// Rendering code must subsequently call VertexPointer etc for all of the streams
|
||
// that were activated in this function, else AssertPointersBound will complain
|
||
// that some arrays were unspecified
|
||
m_ValidStreams = 0;
|
||
}
|
||
|
||
void CShaderProgram::UnbindClientStates()
|
||
{
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION))
|
||
glDisableClientState(GL_VERTEX_ARRAY);
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL))
|
||
glDisableClientState(GL_NORMAL_ARRAY);
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR))
|
||
glDisableClientState(GL_COLOR_ARRAY);
|
||
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0))
|
||
{
|
||
glClientActiveTextureARB(GL_TEXTURE0);
|
||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||
}
|
||
|
||
if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1))
|
||
{
|
||
glClientActiveTextureARB(GL_TEXTURE1);
|
||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||
glClientActiveTextureARB(GL_TEXTURE0);
|
||
}
|
||
}
|
||
|
||
#endif // !CONFIG2_GLES
|
||
|
||
bool CShaderProgram::IsStreamActive(const VertexAttributeStream stream) const
|
||
{
|
||
return (m_StreamFlags & GetStreamMask(stream)) != 0;
|
||
}
|
||
|
||
void CShaderProgram::VertexAttribPointer(
|
||
const VertexAttributeStream stream, const Format format,
|
||
const uint32_t offset, const uint32_t stride,
|
||
const VertexAttributeRate rate, const void* data)
|
||
{
|
||
ENSURE(rate == VertexAttributeRate::PER_VERTEX);
|
||
switch (stream)
|
||
{
|
||
case VertexAttributeStream::POSITION:
|
||
VertexPointer(format, stride, static_cast<const u8*>(data) + offset);
|
||
break;
|
||
case VertexAttributeStream::NORMAL:
|
||
NormalPointer(format, stride, static_cast<const u8*>(data) + offset);
|
||
break;
|
||
case VertexAttributeStream::COLOR:
|
||
ColorPointer(format, stride, static_cast<const u8*>(data) + offset);
|
||
break;
|
||
case VertexAttributeStream::UV0: FALLTHROUGH;
|
||
case VertexAttributeStream::UV1: FALLTHROUGH;
|
||
case VertexAttributeStream::UV2: FALLTHROUGH;
|
||
case VertexAttributeStream::UV3: FALLTHROUGH;
|
||
case VertexAttributeStream::UV4: FALLTHROUGH;
|
||
case VertexAttributeStream::UV5: FALLTHROUGH;
|
||
case VertexAttributeStream::UV6: FALLTHROUGH;
|
||
case VertexAttributeStream::UV7:
|
||
{
|
||
const int indexOffset = static_cast<int>(stream) - static_cast<int>(VertexAttributeStream::UV0);
|
||
TexCoordPointer(GL_TEXTURE0 + indexOffset, format, stride, static_cast<const u8*>(data) + offset);
|
||
break;
|
||
}
|
||
default:
|
||
debug_warn("Unsupported stream");
|
||
};
|
||
}
|
||
|
||
void CShaderProgram::AssertPointersBound()
|
||
{
|
||
ENSURE((m_StreamFlags & ~m_ValidStreams) == 0);
|
||
}
|
||
|
||
} // namespace GL
|
||
|
||
} // namespace Backend
|
||
|
||
} // namespace Renderer
|