2026-02-19 09:20:09 -08:00
|
|
|
/* Copyright (C) 2026 Wildfire Games.
|
2023-12-02 16:30:12 -08:00
|
|
|
* This file is part of 0 A.D.
|
2022-03-14 15:16:14 -07:00
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
2022-03-14 15:16:14 -07:00
|
|
|
* 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.
|
|
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
2022-03-14 15:16:14 -07:00
|
|
|
* 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
|
2023-12-02 16:30:12 -08:00
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
2022-03-14 15:16:14 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
|
|
#include "ShaderProgram.h"
|
|
|
|
|
|
|
|
|
|
#include "graphics/PreprocessorWrapper.h"
|
2025-07-10 12:03:11 -07:00
|
|
|
#include "graphics/ShaderDefines.h"
|
|
|
|
|
#include "lib/config2.h"
|
|
|
|
|
#include "lib/path.h"
|
|
|
|
|
#include "lib/types.h"
|
|
|
|
|
#include "lib/utf8.h"
|
2022-03-14 15:16:14 -07:00
|
|
|
#include "ps/CLogger.h"
|
2025-07-10 12:03:11 -07:00
|
|
|
#include "ps/Errors.h"
|
2022-03-14 15:16:14 -07:00
|
|
|
#include "ps/Filesystem.h"
|
2025-07-10 12:03:11 -07:00
|
|
|
#include "ps/Profiler2.h"
|
|
|
|
|
#include "ps/XMB/XMBData.h"
|
|
|
|
|
#include "ps/XMB/XMBStorage.h"
|
2022-03-14 15:16:14 -07:00
|
|
|
#include "ps/XML/Xeromyces.h"
|
2025-07-10 12:03:11 -07:00
|
|
|
#include "renderer/backend/PipelineState.h"
|
2024-12-09 13:47:15 -08:00
|
|
|
#include "renderer/backend/gl/Buffer.h"
|
2022-03-14 15:16:14 -07:00
|
|
|
#include "renderer/backend/gl/DeviceCommandContext.h"
|
|
|
|
|
|
2025-07-10 12:03:11 -07:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
2022-03-14 15:16:14 -07:00
|
|
|
#define USE_SHADER_XML_VALIDATION 1
|
|
|
|
|
|
|
|
|
|
#if USE_SHADER_XML_VALIDATION
|
|
|
|
|
#include "ps/XML/XMLWriter.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
namespace Renderer
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
namespace Backend
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
namespace GL
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
|
2022-05-02 13:57:22 -07:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-23 13:11:14 -07:00
|
|
|
int GetStreamMask(const VertexAttributeStream stream)
|
|
|
|
|
{
|
|
|
|
|
return 1 << static_cast<int>(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GLint GLSizeFromFormat(const Format format)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
|
|
|
|
GLint size = 1;
|
|
|
|
|
if (format == Renderer::Backend::Format::R32_SFLOAT ||
|
2024-12-09 13:47:15 -08:00
|
|
|
format == Renderer::Backend::Format::R16_SINT ||
|
|
|
|
|
format == Renderer::Backend::Format::R16_SFLOAT)
|
2022-03-14 15:16:14 -07:00
|
|
|
size = 1;
|
|
|
|
|
else if (
|
|
|
|
|
format == Renderer::Backend::Format::R8G8_UNORM ||
|
|
|
|
|
format == Renderer::Backend::Format::R8G8_UINT ||
|
|
|
|
|
format == Renderer::Backend::Format::R16G16_SINT ||
|
2024-12-09 13:47:15 -08:00
|
|
|
format == Renderer::Backend::Format::R16G16_SFLOAT ||
|
2022-03-14 15:16:14 -07:00
|
|
|
format == Renderer::Backend::Format::R32G32_SFLOAT)
|
|
|
|
|
size = 2;
|
2024-12-09 13:47:15 -08:00
|
|
|
else if (
|
|
|
|
|
format == Renderer::Backend::Format::R16G16B16_SFLOAT ||
|
|
|
|
|
format == Renderer::Backend::Format::R32G32B32_SFLOAT)
|
2022-03-14 15:16:14 -07:00
|
|
|
size = 3;
|
|
|
|
|
else if (
|
2024-12-09 13:47:15 -08:00
|
|
|
format == Renderer::Backend::Format::R16G16B16A16_SFLOAT ||
|
2022-03-14 15:16:14 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-23 13:11:14 -07:00
|
|
|
GLenum GLTypeFromFormat(const Format format)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
|
|
|
|
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;
|
2024-12-09 13:47:15 -08:00
|
|
|
#if !CONFIG2_GLES
|
|
|
|
|
else if (format == Renderer::Backend::Format::R16_SFLOAT ||
|
|
|
|
|
format == Renderer::Backend::Format::R16G16_SFLOAT ||
|
|
|
|
|
format == Renderer::Backend::Format::R16G16B16_SFLOAT ||
|
|
|
|
|
format == Renderer::Backend::Format::R16G16B16A16_SFLOAT)
|
|
|
|
|
type = GL_HALF_FLOAT;
|
|
|
|
|
#endif
|
2022-03-14 15:16:14 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-23 13:11:14 -07:00
|
|
|
GLboolean NormalizedFromFormat(const Format format)
|
|
|
|
|
{
|
|
|
|
|
switch (format)
|
|
|
|
|
{
|
2025-01-29 03:47:21 -08:00
|
|
|
case Format::R8G8_UNORM:
|
|
|
|
|
case Format::R8G8B8_UNORM:
|
|
|
|
|
case Format::R8G8B8A8_UNORM:
|
|
|
|
|
case Format::R16_UNORM:
|
2022-04-23 13:11:14 -07:00
|
|
|
case Format::R16G16_UNORM:
|
|
|
|
|
return GL_TRUE;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return GL_FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetAttributeLocationFromStream(
|
|
|
|
|
CDevice* device, const VertexAttributeStream stream)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-12 09:13:47 -07:00
|
|
|
// 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)
|
|
|
|
|
{
|
2022-04-23 13:11:14 -07:00
|
|
|
return static_cast<int>(stream);
|
2022-04-12 09:13:47 -07:00
|
|
|
}
|
|
|
|
|
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
|
2022-04-15 11:13:33 -07:00
|
|
|
switch (stream)
|
|
|
|
|
{
|
2022-04-23 13:11:14 -07:00
|
|
|
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;
|
2022-04-15 11:13:33 -07:00
|
|
|
}
|
2022-04-12 09:13:47 -07:00
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
|
|
|
|
debug_warn("Invalid attribute semantics");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-15 10:45:15 -07:00
|
|
|
bool PreprocessShaderFile(
|
2026-02-19 09:20:09 -08:00
|
|
|
const CShaderDefines& defines, const VfsPath& path, const char* stage,
|
2022-04-15 10:45:15 -07:00
|
|
|
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(
|
2026-02-19 09:20:09 -08:00
|
|
|
[&fileDependencies](const CStr& includePath, CStr& out) -> bool
|
2022-04-15 10:45:15 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
const VfsPath includeFilePath{L"shaders/glsl/" + wstring_from_utf8(includePath)};
|
2022-04-15 10:45:15 -07:00
|
|
|
// 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);
|
2026-02-19 09:20:09 -08:00
|
|
|
preprocessor.AddDefine(stage, "1");
|
2022-04-15 10:45:15 -07:00
|
|
|
|
|
|
|
|
#if CONFIG2_GLES
|
2026-02-19 09:20:09 -08:00
|
|
|
// 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");
|
2022-04-15 10:45:15 -07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
source = preprocessor.Preprocess(file.GetAsString());
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-27 14:27:56 -07:00
|
|
|
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)
|
2023-06-30 03:02:24 -07:00
|
|
|
LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog.get());
|
2023-06-27 14:27:56 -07:00
|
|
|
else
|
2023-06-30 03:02:24 -07:00
|
|
|
LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog.get());
|
2023-06-27 14:27:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-14 15:16:14 -07:00
|
|
|
} // anonymous namespace
|
|
|
|
|
|
2023-01-05 16:39:25 -08:00
|
|
|
IDevice* CVertexInputLayout::GetDevice()
|
|
|
|
|
{
|
|
|
|
|
return m_Device;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
CShaderProgram::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) :
|
|
|
|
|
m_StreamFlags(streamflags), m_ValidStreams(0),
|
|
|
|
|
m_Device(device), m_Name(name),
|
|
|
|
|
m_VertexAttribs(vertexAttribs)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
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());
|
2022-05-02 13:57:22 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_Program = 0;
|
|
|
|
|
m_FileDependencies = {programPath};
|
|
|
|
|
for (const auto& shaderStage : shaderStages)
|
|
|
|
|
m_FileDependencies.emplace_back(std::get<0>(shaderStage));
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
// TODO: replace by scoped bind.
|
|
|
|
|
m_Device->GetActiveCommandContext()->SetGraphicsPipelineState(
|
|
|
|
|
MakeDefaultGraphicsPipelineStateDesc());
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
std::vector<VfsPath> newFileDependencies = {programPath};
|
|
|
|
|
for (const auto& [path, type] : shaderStages)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
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)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
case GL_VERTEX_SHADER:
|
|
|
|
|
stageDefine = "STAGE_VERTEX";
|
|
|
|
|
break;
|
|
|
|
|
case GL_FRAGMENT_SHADER:
|
|
|
|
|
stageDefine = "STAGE_FRAGMENT";
|
|
|
|
|
break;
|
|
|
|
|
#if !CONFIG2_GLES
|
|
|
|
|
case GL_COMPUTE_SHADER:
|
|
|
|
|
stageDefine = "STAGE_COMPUTE";
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
CStr source;
|
|
|
|
|
if (!PreprocessShaderFile(defines, path, stageDefine, source, newFileDependencies))
|
2022-05-02 13:57:22 -07:00
|
|
|
return;
|
2026-02-19 09:20:09 -08:00
|
|
|
if (source.empty())
|
2022-05-02 13:57:22 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("Failed to preprocess shader: '%s'", path.string8());
|
2022-05-02 13:57:22 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
#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))
|
2022-05-02 13:57:22 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_FileDependencies = std::move(newFileDependencies);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (!Link(programPath))
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-05-02 13:57:22 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
CShaderProgram::~CShaderProgram()
|
|
|
|
|
{
|
|
|
|
|
if (m_Program)
|
|
|
|
|
glDeleteProgram(m_Program);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
for (ShaderStage& stage : m_ShaderStages)
|
|
|
|
|
glDeleteShader(stage.shader);
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
bool CShaderProgram::Link(const VfsPath& path)
|
|
|
|
|
{
|
|
|
|
|
ENSURE(!m_Program);
|
|
|
|
|
m_Program = glCreateProgram();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
#if !CONFIG2_GLES
|
|
|
|
|
if (m_Device->GetCapabilities().debugLabels)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
glObjectLabel(GL_PROGRAM, m_Program, -1, m_Name.c_str());
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
#endif
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
for (ShaderStage& stage : m_ShaderStages)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
glAttachShader(m_Program, stage.shader);
|
|
|
|
|
ogl_WarnIfError();
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
// 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());
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
glLinkProgram(m_Program);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
GLint ok = 0;
|
|
|
|
|
glGetProgramiv(m_Program, GL_LINK_STATUS, &ok);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
GLint length = 0;
|
|
|
|
|
glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (!ok && length == 0)
|
|
|
|
|
length = 4096;
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (length > 1)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
char* infolog = new char[length];
|
|
|
|
|
glGetProgramInfoLog(m_Program, length, NULL, infolog);
|
2022-04-11 15:10:21 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
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);
|
2022-04-11 15:10:21 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
delete[] infolog;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
ogl_WarnIfError();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (!ok)
|
|
|
|
|
return false;
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
Bind(nullptr);
|
2022-04-11 13:30:23 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
ogl_WarnIfError();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
// 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},
|
|
|
|
|
};
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
std::vector<uint8_t> occupiedUnits;
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
#if !CONFIG2_GLES
|
|
|
|
|
const bool isStorageSupported{m_Device->GetCapabilities().storage};
|
|
|
|
|
if (isStorageSupported)
|
|
|
|
|
{
|
|
|
|
|
constexpr GLint maxBlockNameLength{128};
|
|
|
|
|
char name[maxBlockNameLength];
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
GLint maxUniformBlockNameLength{0};
|
|
|
|
|
glGetProgramInterfaceiv(m_Program, GL_UNIFORM_BLOCK, GL_MAX_NAME_LENGTH, &maxUniformBlockNameLength);
|
|
|
|
|
ogl_WarnIfError();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
GLint numberOfActiveUniformBlocks{0};
|
|
|
|
|
glGetProgramInterfaceiv(m_Program, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &numberOfActiveUniformBlocks);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
// Currently we support the only one uniform buffer per shader.
|
|
|
|
|
if (numberOfActiveUniformBlocks == 1)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
GLsizei length{0};
|
|
|
|
|
glGetProgramResourceName(m_Program, GL_UNIFORM_BLOCK, 0, maxBlockNameLength, &length, name);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
const GLuint location{glGetProgramResourceIndex(m_Program, GL_UNIFORM_BLOCK, name)};
|
|
|
|
|
glUniformBlockBinding(m_Program, location, location);
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_UniformBufferLocation = location;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
GLint maxStorageNameLength{0};
|
|
|
|
|
glGetProgramInterfaceiv(m_Program, GL_SHADER_STORAGE_BLOCK, GL_MAX_NAME_LENGTH, &maxStorageNameLength);
|
2022-03-14 15:16:14 -07:00
|
|
|
ogl_WarnIfError();
|
2026-02-19 09:20:09 -08:00
|
|
|
ENSURE(maxStorageNameLength <= maxBlockNameLength);
|
|
|
|
|
GLint numberOfActiveStorages{0};
|
|
|
|
|
glGetProgramInterfaceiv(m_Program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numberOfActiveStorages);
|
2022-03-14 15:16:14 -07:00
|
|
|
ogl_WarnIfError();
|
2026-02-19 09:20:09 -08:00
|
|
|
for (GLint index{0}; index < numberOfActiveStorages; ++index)
|
2024-12-09 13:47:15 -08:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
GLsizei length{0};
|
|
|
|
|
glGetProgramResourceName(m_Program, GL_SHADER_STORAGE_BLOCK, index, maxBlockNameLength, &length, name);
|
2024-12-09 13:47:15 -08:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
const GLuint location{glGetProgramResourceIndex(m_Program, GL_SHADER_STORAGE_BLOCK, name)};
|
|
|
|
|
glShaderStorageBlockBinding(m_Program, location, location);
|
2024-12-09 13:47:15 -08:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
const CStrIntern nameIntern(name);
|
2024-12-09 13:47:15 -08:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_BindingSlotsMapping[nameIntern] = m_BindingSlots.size();
|
|
|
|
|
BindingSlot bindingSlot{};
|
|
|
|
|
bindingSlot.name = nameIntern;
|
|
|
|
|
bindingSlot.location = location;
|
|
|
|
|
bindingSlot.isStorageBuffer = true;
|
|
|
|
|
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
2024-12-09 13:47:15 -08:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
}
|
2024-12-09 13:47:15 -08:00
|
|
|
#endif
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
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);
|
2022-03-14 15:16:14 -07:00
|
|
|
ogl_WarnIfError();
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
const GLint location = glGetUniformLocation(m_Program, name);
|
2022-04-23 14:39:15 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
// 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;
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
const CStrIntern nameIntern(name);
|
2022-05-02 13:57:22 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_BindingSlotsMapping[nameIntern] = m_BindingSlots.size();
|
|
|
|
|
BindingSlot bindingSlot{};
|
|
|
|
|
bindingSlot.name = nameIntern;
|
|
|
|
|
bindingSlot.location = location;
|
|
|
|
|
bindingSlot.size = size;
|
|
|
|
|
bindingSlot.type = type;
|
|
|
|
|
bindingSlot.isTexture = false;
|
|
|
|
|
bindingSlot.isStorageBuffer = false;
|
2022-05-02 13:57:22 -07:00
|
|
|
|
|
|
|
|
#define CASE(TYPE, ELEMENT_TYPE, ELEMENT_COUNT) \
|
|
|
|
|
case GL_ ## TYPE: \
|
|
|
|
|
bindingSlot.elementType = GL_ ## ELEMENT_TYPE; \
|
|
|
|
|
bindingSlot.elementCount = ELEMENT_COUNT; \
|
|
|
|
|
break;
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
switch (type)
|
|
|
|
|
{
|
2022-05-02 13:57:22 -07:00
|
|
|
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
|
2026-02-19 09:20:09 -08:00
|
|
|
}
|
2022-05-02 13:57:22 -07:00
|
|
|
#undef CASE
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
// Assign sampler uniforms to sequential texture units.
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case GL_SAMPLER_2D:
|
|
|
|
|
bindingSlot.elementType = GL_TEXTURE_2D;
|
|
|
|
|
bindingSlot.isTexture = true;
|
|
|
|
|
break;
|
|
|
|
|
case GL_SAMPLER_CUBE:
|
|
|
|
|
bindingSlot.elementType = GL_TEXTURE_CUBE_MAP;
|
|
|
|
|
bindingSlot.isTexture = true;
|
|
|
|
|
break;
|
2024-12-09 13:47:15 -08:00
|
|
|
#if !CONFIG2_GLES
|
2026-02-19 09:20:09 -08:00
|
|
|
case GL_SAMPLER_2D_SHADOW:
|
|
|
|
|
bindingSlot.elementType = GL_TEXTURE_2D;
|
|
|
|
|
bindingSlot.isTexture = true;
|
|
|
|
|
break;
|
|
|
|
|
case GL_IMAGE_2D:
|
|
|
|
|
bindingSlot.elementType = GL_IMAGE_2D;
|
|
|
|
|
bindingSlot.isTexture = true;
|
|
|
|
|
m_HasImageUniforms = true;
|
|
|
|
|
break;
|
2024-12-09 13:47:15 -08:00
|
|
|
#endif
|
2026-02-19 09:20:09 -08:00
|
|
|
default:
|
|
|
|
|
break;
|
2022-05-02 13:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (bindingSlot.isTexture)
|
2022-05-02 13:57:22 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
const auto it = requiredUnits.find(nameIntern);
|
|
|
|
|
const int unit = it == requiredUnits.end() ? -1 : it->second;
|
|
|
|
|
bindingSlot.elementCount = unit;
|
|
|
|
|
if (unit != -1)
|
2022-05-02 13:57:22 -07:00
|
|
|
{
|
|
|
|
|
if (unit >= static_cast<int>(occupiedUnits.size()))
|
|
|
|
|
occupiedUnits.resize(unit + 1);
|
|
|
|
|
occupiedUnits[unit] = true;
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (bindingSlot.elementType == 0)
|
2024-12-09 13:47:15 -08:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("CShaderProgram::Link: unsupported uniform type: 0x%04x", static_cast<int>(type));
|
2024-12-09 13:47:15 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
#if !CONFIG2_GLES
|
|
|
|
|
if (isStorageSupported)
|
|
|
|
|
{
|
|
|
|
|
GLuint uniformIndex{0};
|
|
|
|
|
const GLchar* nameToQuery{name};
|
|
|
|
|
glGetUniformIndices(m_Program, 1, &nameToQuery, &uniformIndex);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
|
|
|
|
|
GLint uniformOffset{0};
|
|
|
|
|
glGetActiveUniformsiv(m_Program, 1, &uniformIndex, GL_UNIFORM_OFFSET, &uniformOffset);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
|
|
|
|
|
// According to the OpenGL spec:
|
|
|
|
|
// https://registry.khronos.org/OpenGL-Refpages/es3/html/glGetActiveUniformsiv.xhtml
|
|
|
|
|
// For uniforms in the default uniform block, -1 will be returned.
|
|
|
|
|
if (uniformOffset >= 0)
|
|
|
|
|
{
|
|
|
|
|
const uint32_t sizeInBytes{static_cast<uint32_t>(bindingSlot.size * bindingSlot.elementCount * sizeof(float))};
|
|
|
|
|
m_UniformBufferSize = std::max(m_UniformBufferSize, uniformOffset + sizeInBytes);
|
|
|
|
|
bindingSlot.location = -1;
|
|
|
|
|
bindingSlot.offset = uniformOffset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_BindingSlots.emplace_back(std::move(bindingSlot));
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
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();
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (m_UniformBufferSize > 0 && m_UniformBufferLocation != -1)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
m_UniformBuffer = m_Device->CreateBuffer(
|
|
|
|
|
"ShaderProgramUniformBuffer", IBuffer::Type::UNIFORM, m_UniformBufferSize,
|
|
|
|
|
IBuffer::Usage::DYNAMIC | IBuffer::Usage::TRANSFER_DST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: verify that we're not using more samplers than is supported
|
|
|
|
|
|
|
|
|
|
Unbind();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::Bind(CShaderProgram* previousShaderProgram)
|
|
|
|
|
{
|
|
|
|
|
ENSURE(this != previousShaderProgram);
|
|
|
|
|
|
|
|
|
|
glUseProgram(m_Program);
|
2025-01-04 06:13:10 -08:00
|
|
|
#if !CONFIG2_GLES
|
2026-02-19 09:20:09 -08:00
|
|
|
if (m_UniformBuffer)
|
|
|
|
|
glBindBufferBase(GL_UNIFORM_BUFFER, m_UniformBufferLocation, m_UniformBuffer->As<CBuffer>()->GetHandle());
|
2025-01-04 06:13:10 -08:00
|
|
|
#endif
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (previousShaderProgram)
|
|
|
|
|
{
|
|
|
|
|
std::vector<int>::iterator itPrevious = previousShaderProgram->m_ActiveVertexAttributes.begin();
|
|
|
|
|
std::vector<int>::iterator itNext = m_ActiveVertexAttributes.begin();
|
|
|
|
|
while (
|
|
|
|
|
itPrevious != previousShaderProgram->m_ActiveVertexAttributes.end() ||
|
|
|
|
|
itNext != m_ActiveVertexAttributes.end())
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
if (itPrevious != previousShaderProgram->m_ActiveVertexAttributes.end() &&
|
2022-03-14 15:16:14 -07:00
|
|
|
itNext != m_ActiveVertexAttributes.end())
|
|
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
if (*itPrevious == *itNext)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
++itPrevious;
|
|
|
|
|
++itNext;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
else if (*itPrevious < *itNext)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
|
|
|
|
glDisableVertexAttribArray(*itPrevious);
|
|
|
|
|
++itPrevious;
|
|
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
else if (*itPrevious > *itNext)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
|
|
|
|
glEnableVertexAttribArray(*itNext);
|
|
|
|
|
++itNext;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
else if (itPrevious != previousShaderProgram->m_ActiveVertexAttributes.end())
|
|
|
|
|
{
|
|
|
|
|
glDisableVertexAttribArray(*itPrevious);
|
|
|
|
|
++itPrevious;
|
|
|
|
|
}
|
|
|
|
|
else if (itNext != m_ActiveVertexAttributes.end())
|
|
|
|
|
{
|
|
|
|
|
glEnableVertexAttribArray(*itNext);
|
|
|
|
|
++itNext;
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
else
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
|
|
|
|
for (const int index : m_ActiveVertexAttributes)
|
2026-02-19 09:20:09 -08:00
|
|
|
glEnableVertexAttribArray(index);
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
m_ValidStreams = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CShaderProgram::Unbind()
|
|
|
|
|
{
|
|
|
|
|
glUseProgram(0);
|
|
|
|
|
|
|
|
|
|
for (const int index : m_ActiveVertexAttributes)
|
|
|
|
|
glDisableVertexAttribArray(index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t CShaderProgram::GetBindingSlot(const CStrIntern name) const
|
|
|
|
|
{
|
|
|
|
|
auto it = m_BindingSlotsMapping.find(name);
|
|
|
|
|
return it == m_BindingSlotsMapping.end() ? -1 : it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CShaderProgram::TextureUnit CShaderProgram::GetTextureUnit(const int32_t bindingSlot)
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GLuint CShaderProgram::GetStorageBuffer(const int32_t bindingSlot)
|
|
|
|
|
{
|
|
|
|
|
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
|
|
|
|
return 0;
|
|
|
|
|
if (!m_BindingSlots[bindingSlot].isStorageBuffer)
|
|
|
|
|
LOGERROR("CShaderProgramGLSL::GetStorageBuffer(): Invalid slot (expected storage buffer): '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return m_BindingSlots[bindingSlot].location;
|
|
|
|
|
}
|
2022-05-08 15:02:46 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::SetUniform(const int32_t bindingSlot, const float value)
|
|
|
|
|
{
|
|
|
|
|
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
|
|
|
|
return;
|
|
|
|
|
if (m_BindingSlots[bindingSlot].type != GL_FLOAT ||
|
|
|
|
|
m_BindingSlots[bindingSlot].size != 1)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
glUniform1f(m_BindingSlots[bindingSlot].location, value);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::SetUniform(
|
|
|
|
|
const int32_t bindingSlot,
|
|
|
|
|
const float valueX, const float valueY)
|
|
|
|
|
{
|
|
|
|
|
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)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec2) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
glUniform2f(m_BindingSlots[bindingSlot].location, valueX, valueY);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::SetUniform(
|
|
|
|
|
const int32_t bindingSlot,
|
|
|
|
|
const float valueX, const float valueY, const float valueZ)
|
|
|
|
|
{
|
|
|
|
|
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)
|
2024-12-09 13:47:15 -08:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("CShaderProgramGLSL::SetUniform(): Invalid uniform type (expected vec3) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return;
|
2024-12-09 13:47:15 -08:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
glUniform3f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
}
|
2024-12-09 13:47:15 -08:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::SetUniform(
|
|
|
|
|
const int32_t bindingSlot,
|
|
|
|
|
const float valueX, const float valueY,
|
|
|
|
|
const float valueZ, const float valueW)
|
|
|
|
|
{
|
|
|
|
|
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)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("CShaderProgram::SetUniform(): Invalid uniform type (expected vec4) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
glUniform4f(m_BindingSlots[bindingSlot].location, valueX, valueY, valueZ, valueW);
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::SetUniform(
|
|
|
|
|
const int32_t bindingSlot, std::span<const float> values)
|
|
|
|
|
{
|
|
|
|
|
if (bindingSlot < 0 || bindingSlot >= static_cast<int32_t>(m_BindingSlots.size()))
|
|
|
|
|
return;
|
|
|
|
|
if (m_BindingSlots[bindingSlot].elementType != GL_FLOAT)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR("CShaderProgram::SetUniform(): Invalid uniform element type (expected float) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
if (m_BindingSlots[bindingSlot].size == 1 && m_BindingSlots[bindingSlot].elementCount > static_cast<GLint>(values.size()))
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
LOGERROR(
|
|
|
|
|
"CShaderProgram::SetUniform(): Invalid uniform element count (expected: %zu passed: %zu) '%s'",
|
|
|
|
|
m_BindingSlots[bindingSlot].elementCount, values.size(), m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
return;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
const GLint location = m_BindingSlots[bindingSlot].location;
|
|
|
|
|
const GLenum type = m_BindingSlots[bindingSlot].type;
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
if (location == -1)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
const uint32_t sizeInBytes{
|
|
|
|
|
static_cast<uint32_t>(m_BindingSlots[bindingSlot].size * m_BindingSlots[bindingSlot].elementCount * sizeof(float))};
|
|
|
|
|
const uint32_t dataSizeToUpload{std::min(
|
|
|
|
|
static_cast<uint32_t>(values.size() * sizeof(float)), sizeInBytes)};
|
|
|
|
|
m_Device->GetActiveCommandContext()->UploadBufferRegion(
|
|
|
|
|
m_UniformBuffer.get(), values.data(), m_BindingSlots[bindingSlot].offset, dataSizeToUpload);
|
|
|
|
|
return;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
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)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
// 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());
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
else
|
|
|
|
|
LOGERROR("CShaderProgram::SetUniform(): Invalid uniform type (expected float, vec2, vec3, vec4, mat4) '%s'", m_BindingSlots[bindingSlot].name.c_str());
|
|
|
|
|
ogl_WarnIfError();
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
void CShaderProgram::VertexAttribPointer(
|
|
|
|
|
const VertexAttributeStream stream, const Format format,
|
|
|
|
|
const uint32_t offset, const uint32_t stride,
|
|
|
|
|
[[maybe_unused]] const VertexAttributeRate rate, const void* data)
|
|
|
|
|
{
|
|
|
|
|
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);
|
2022-05-26 09:36:57 -07:00
|
|
|
#if CONFIG2_GLES
|
2026-02-19 09:20:09 -08:00
|
|
|
ENSURE(!m_Device->GetCapabilities().instancing);
|
2022-05-26 09:36:57 -07:00
|
|
|
#else
|
2026-02-19 09:20:09 -08:00
|
|
|
if (rate == VertexAttributeRate::PER_INSTANCE)
|
|
|
|
|
ENSURE(m_Device->GetCapabilities().instancing);
|
|
|
|
|
if (m_Device->GetCapabilities().instancing)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
glVertexAttribDivisorARB(attributeLocation, rate == VertexAttributeRate::PER_INSTANCE ? 1 : 0);
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
#endif
|
|
|
|
|
m_ValidStreams |= GetStreamMask(stream);
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
std::vector<VfsPath> CShaderProgram::GetFileDependencies() const
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
return m_FileDependencies;
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
2024-09-18 09:17:04 -07:00
|
|
|
if (!g_Xeromyces.ValidateEncoded("shader", name, shaderFile.GetOutput()))
|
2022-03-14 15:16:14 -07:00
|
|
|
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)
|
2024-01-17 11:40:27 -08:00
|
|
|
EL(compute);
|
2022-03-14 15:16:14 -07:00
|
|
|
EL(define);
|
|
|
|
|
EL(fragment);
|
|
|
|
|
EL(stream);
|
|
|
|
|
EL(uniform);
|
|
|
|
|
EL(vertex);
|
2022-04-15 11:13:33 -07:00
|
|
|
AT(attribute);
|
2022-03-14 15:16:14 -07:00
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
VfsPath vertexFile;
|
|
|
|
|
VfsPath fragmentFile;
|
|
|
|
|
CShaderDefines defines = baseDefines;
|
2022-05-02 13:57:22 -07:00
|
|
|
std::map<CStrIntern, std::pair<CStr, int>> vertexUniforms;
|
|
|
|
|
std::map<CStrIntern, std::pair<CStr, int>> fragmentUniforms;
|
2022-03-14 15:16:14 -07:00
|
|
|
std::map<CStrIntern, int> vertexAttribs;
|
|
|
|
|
int streamFlags = 0;
|
|
|
|
|
|
2024-01-17 11:40:27 -08:00
|
|
|
VfsPath computeFile;
|
|
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
XERO_ITER_EL(root, child)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
if (child.GetNodeName() == el_define)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
defines.Add(CStrIntern(child.GetAttributes().GetNamedItem(at_name)), CStrIntern(child.GetAttributes().GetNamedItem(at_value)));
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (child.GetNodeName() == el_vertex)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
vertexFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
XERO_ITER_EL(child, param)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
XMBAttributeList attributes = param.GetAttributes();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
CStr cond = attributes.GetNamedItem(at_if);
|
2022-03-14 15:16:14 -07:00
|
|
|
if (!cond.empty() && !preprocessor.TestConditional(cond))
|
|
|
|
|
continue;
|
|
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
if (param.GetNodeName() == el_uniform)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-05-02 13:57:22 -07:00
|
|
|
vertexUniforms[CStrIntern(attributes.GetNamedItem(at_name))] =
|
|
|
|
|
std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt());
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (param.GetNodeName() == el_stream)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
const CStr streamName = attributes.GetNamedItem(at_name);
|
|
|
|
|
const CStr attributeName = attributes.GetNamedItem(at_attribute);
|
2026-02-19 09:20:09 -08:00
|
|
|
if (attributeName.empty())
|
2022-04-15 11:13:33 -07:00
|
|
|
LOGERROR("Empty attribute name in vertex shader description '%s'", vertexFile.string8().c_str());
|
|
|
|
|
|
2022-04-23 13:11:14 -07:00
|
|
|
VertexAttributeStream stream =
|
|
|
|
|
VertexAttributeStream::UV7;
|
2022-04-15 11:13:33 -07:00
|
|
|
if (streamName == "pos")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::POSITION;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "normal")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::NORMAL;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "color")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::COLOR;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv0")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV0;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv1")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV1;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv2")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV2;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv3")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV3;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv4")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV4;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv5")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV5;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv6")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV6;
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (streamName == "uv7")
|
2022-04-23 13:11:14 -07:00
|
|
|
stream = VertexAttributeStream::UV7;
|
2022-04-15 11:13:33 -07:00
|
|
|
else
|
|
|
|
|
LOGERROR("Unknown stream '%s' in vertex shader description '%s'", streamName.c_str(), vertexFile.string8().c_str());
|
|
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
const int attributeLocation = GetAttributeLocationFromStream(device, stream);
|
|
|
|
|
vertexAttribs[CStrIntern(attributeName)] = attributeLocation;
|
2022-04-23 13:11:14 -07:00
|
|
|
streamFlags |= GetStreamMask(stream);
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-15 11:13:33 -07:00
|
|
|
else if (child.GetNodeName() == el_fragment)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
fragmentFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
XERO_ITER_EL(child, param)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
XMBAttributeList attributes = param.GetAttributes();
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
CStr cond = attributes.GetNamedItem(at_if);
|
2022-03-14 15:16:14 -07:00
|
|
|
if (!cond.empty() && !preprocessor.TestConditional(cond))
|
|
|
|
|
continue;
|
|
|
|
|
|
2022-04-15 11:13:33 -07:00
|
|
|
if (param.GetNodeName() == el_uniform)
|
2022-03-14 15:16:14 -07:00
|
|
|
{
|
2022-04-15 11:13:33 -07:00
|
|
|
fragmentUniforms[CStrIntern(attributes.GetNamedItem(at_name))] =
|
2022-05-02 13:57:22 -07:00
|
|
|
std::make_pair(attributes.GetNamedItem(at_type), attributes.GetNamedItem(at_loc).ToInt());
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-17 11:40:27 -08:00
|
|
|
else if (child.GetNodeName() == el_compute)
|
|
|
|
|
{
|
|
|
|
|
computeFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8();
|
|
|
|
|
}
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-12 07:31:43 -07:00
|
|
|
#if !CONFIG2_GLES
|
2026-02-19 09:20:09 -08:00
|
|
|
if (!computeFile.empty())
|
2022-05-02 13:57:22 -07:00
|
|
|
{
|
2026-02-19 09:20:09 -08:00
|
|
|
ENSURE(streamFlags == 0);
|
|
|
|
|
ENSURE(vertexAttribs.empty());
|
2022-05-02 13:57:22 -07:00
|
|
|
}
|
2026-02-19 09:20:09 -08:00
|
|
|
const PS::StaticVector<std::tuple<VfsPath, GLenum>, 2> shaderStages{computeFile.empty()
|
|
|
|
|
? PS::StaticVector<std::tuple<VfsPath, GLenum>, 2>{{vertexFile, GL_VERTEX_SHADER}, {fragmentFile, GL_FRAGMENT_SHADER}}
|
|
|
|
|
: PS::StaticVector<std::tuple<VfsPath, GLenum>, 2>{{computeFile, GL_COMPUTE_SHADER}}};
|
2022-03-14 15:16:14 -07:00
|
|
|
#else
|
2026-02-19 09:20:09 -08:00
|
|
|
const PS::StaticVector<std::tuple<VfsPath, GLenum>, 2> shaderStages{{{vertexFile, GL_VERTEX_SHADER}, {fragmentFile, GL_FRAGMENT_SHADER}}};
|
|
|
|
|
#endif
|
2022-03-14 15:16:14 -07:00
|
|
|
|
2026-02-19 09:20:09 -08:00
|
|
|
return std::unique_ptr<CShaderProgram>(new CShaderProgram(
|
|
|
|
|
device, name, xmlFilename, shaderStages, defines,
|
|
|
|
|
vertexAttribs, streamFlags));
|
2022-03-14 15:16:14 -07:00
|
|
|
}
|
|
|
|
|
|
2022-04-23 13:11:14 -07:00
|
|
|
bool CShaderProgram::IsStreamActive(const VertexAttributeStream stream) const
|
|
|
|
|
{
|
|
|
|
|
return (m_StreamFlags & GetStreamMask(stream)) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-14 15:16:14 -07:00
|
|
|
void CShaderProgram::AssertPointersBound()
|
|
|
|
|
{
|
|
|
|
|
ENSURE((m_StreamFlags & ~m_ValidStreams) == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace GL
|
|
|
|
|
|
|
|
|
|
} // namespace Backend
|
|
|
|
|
|
|
|
|
|
} // namespace Renderer
|