0ad/source/renderer/backend/gl/DeviceCommandContext.cpp
Vladislav Belov f781181899
Adds sRGB support for proper lighting
Currently we're incorrectly using sRGB colors as raw inputs for lighting
instead of conversion to linear space. For proper PBR we need linear
values.
2026-07-29 01:29:09 +02:00

1464 lines
46 KiB
C++

/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* 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 "DeviceCommandContext.h"
#include "lib/config2.h"
#include "lib/debug.h"
#include "ps/CLogger.h"
#include "renderer/backend/Barrier.h"
#include "renderer/backend/Format.h"
#include "renderer/backend/IFramebuffer.h"
#include "renderer/backend/IShaderProgram.h"
#include "renderer/backend/ITexture.h"
#include "renderer/backend/PipelineState.h"
#include "renderer/backend/Sampler.h"
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Mapping.h"
#include "renderer/backend/gl/PipelineState.h"
#include "renderer/backend/gl/ShaderProgram.h"
#include "renderer/backend/gl/Texture.h"
#include <algorithm>
#include <cstring>
#include <limits>
#include <vector>
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
bool operator==(const SStencilOpState& lhs, const SStencilOpState& rhs)
{
return
lhs.failOp == rhs.failOp &&
lhs.passOp == rhs.passOp &&
lhs.depthFailOp == rhs.depthFailOp &&
lhs.compareOp == rhs.compareOp;
}
bool operator!=(const SStencilOpState& lhs, const SStencilOpState& rhs)
{
return !operator==(lhs, rhs);
}
bool operator==(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return
lhs.x == rhs.x && lhs.y == rhs.y &&
lhs.width == rhs.width && lhs.height == rhs.height;
}
bool operator!=(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return !operator==(lhs, rhs);
}
void ApplyDepthMask(const bool depthWriteEnabled)
{
glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE);
}
void ApplyColorMask(const uint8_t colorWriteMask)
{
glColorMask(
(colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE);
}
void ApplyStencilMask(const uint32_t stencilWriteMask)
{
glStencilMask(stencilWriteMask);
}
GLenum BufferTypeToGLTarget(const CBuffer::Type type)
{
GLenum target = GL_ARRAY_BUFFER;
switch (type)
{
case CBuffer::Type::VERTEX:
target = GL_ARRAY_BUFFER;
break;
case CBuffer::Type::INDEX:
target = GL_ELEMENT_ARRAY_BUFFER;
break;
case CBuffer::Type::UNIFORM:
#if !CONFIG2_GLES
target = GL_UNIFORM_BUFFER;
#else
debug_warn("Unsupported buffer type.");
#endif
break;
case CBuffer::Type::UPLOAD:
debug_warn("Unsupported buffer type.");
break;
};
return target;
}
void UploadDynamicBufferRegionImpl(
const GLenum target, const uint32_t bufferSize,
const uint32_t dataOffset, const uint32_t dataSize,
const CDeviceCommandContext::UploadBufferFunction& uploadFunction)
{
ENSURE(dataOffset < dataSize);
// Tell the driver that it can reallocate the whole VBO
glBufferData(target, bufferSize, nullptr, GL_DYNAMIC_DRAW);
while (true)
{
// (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used
// here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with
// current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do
// the explicit glBufferData.)
void* mappedData = glMapBuffer(target, GL_WRITE_ONLY);
if (mappedData == nullptr)
{
// This shouldn't happen unless we run out of virtual address space
LOGERROR("glMapBuffer failed");
break;
}
uploadFunction(static_cast<u8*>(mappedData) + dataOffset);
if (glUnmapBuffer(target) == GL_TRUE)
break;
// Unmap might fail on e.g. resolution switches, so just try again
// and hope it will eventually succeed
LOGMESSAGE("glUnmapBuffer failed, trying again...\n");
}
}
/**
* In case we don't need a framebuffer content (because of the following clear
* or overwriting by a shader) we might give a hint to a driver via
* glInvalidateFramebuffer.
*/
void InvalidateFramebuffer(
CFramebuffer* framebuffer, const bool color, const bool depthStencil)
{
GLsizei numberOfAttachments = 0;
GLenum attachments[8];
const bool isBackbuffer = framebuffer->GetHandle() == 0;
if (color && (framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT))
{
if (isBackbuffer)
#if CONFIG2_GLES
attachments[numberOfAttachments++] = GL_COLOR_EXT;
#else
attachments[numberOfAttachments++] = GL_COLOR;
#endif
else
attachments[numberOfAttachments++] = GL_COLOR_ATTACHMENT0;
}
if (depthStencil)
{
if (isBackbuffer)
{
if (framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT)
#if CONFIG2_GLES
attachments[numberOfAttachments++] = GL_DEPTH_EXT;
#else
attachments[numberOfAttachments++] = GL_DEPTH;
#endif
if (framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT)
#if CONFIG2_GLES
attachments[numberOfAttachments++] = GL_STENCIL_EXT;
#else
attachments[numberOfAttachments++] = GL_STENCIL;
#endif
}
else
{
if (framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT)
attachments[numberOfAttachments++] = GL_DEPTH_ATTACHMENT;
if (framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT)
attachments[numberOfAttachments++] = GL_STENCIL_ATTACHMENT;
}
}
if (numberOfAttachments > 0)
{
#if CONFIG2_GLES
glDiscardFramebufferEXT(GL_FRAMEBUFFER_EXT, numberOfAttachments, attachments);
#else
glInvalidateFramebuffer(GL_FRAMEBUFFER_EXT, numberOfAttachments, attachments);
#endif
}
}
} // anonymous namespace
// static
std::unique_ptr<CDeviceCommandContext> CDeviceCommandContext::Create(CDevice* device)
{
std::unique_ptr<CDeviceCommandContext> deviceCommandContext(new CDeviceCommandContext(device));
deviceCommandContext->ResetStates();
return deviceCommandContext;
}
CDeviceCommandContext::CDeviceCommandContext(CDevice* device)
: m_Device(device)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
for (BindUnit& unit : m_BoundTextures)
{
unit.target = GL_TEXTURE_2D;
unit.handle = 0;
}
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
m_VertexAttributeFormat[index].active = false;
m_VertexAttributeFormat[index].initialized = false;
m_VertexAttributeFormat[index].bindingSlot = 0;
}
for (size_t index = 0; index < m_BoundBuffers.size(); ++index)
{
const CBuffer::Type type = static_cast<CBuffer::Type>(index);
// Currently we don't support upload buffers for GL.
if (type == CBuffer::Type::UPLOAD)
continue;
#if CONFIG2_GLES
if (type == CBuffer::Type::UNIFORM)
continue;
#endif
const GLenum target = BufferTypeToGLTarget(type);
const GLuint handle = 0;
m_BoundBuffers[index].first = target;
m_BoundBuffers[index].second = handle;
}
}
CDeviceCommandContext::~CDeviceCommandContext() = default;
IDevice* CDeviceCommandContext::GetDevice()
{
return m_Device;
}
void CDeviceCommandContext::SetGraphicsPipelineState(
const SGraphicsPipelineStateDesc& pipelineState)
{
ENSURE(!pipelineState.shaderProgram || m_InsideFramebufferPass);
SetGraphicsPipelineStateImpl(pipelineState, false);
}
void CDeviceCommandContext::SetGraphicsPipelineState(
IGraphicsPipelineState* pipelineState)
{
ENSURE(!pipelineState->GetShaderProgram() || m_InsideFramebufferPass);
ENSURE(pipelineState);
SetGraphicsPipelineStateImpl(
pipelineState->As<CGraphicsPipelineState>()->GetDesc(), false);
}
void CDeviceCommandContext::SetComputePipelineState(
IComputePipelineState* pipelineState)
{
ENSURE(m_InsideComputePass);
ENSURE(pipelineState);
const SComputePipelineStateDesc& desc = pipelineState->As<CComputePipelineState>()->GetDesc();
if (m_ComputePipelineStateDesc.shaderProgram != desc.shaderProgram)
{
CShaderProgram* currentShaderProgram = nullptr;
if (m_ComputePipelineStateDesc.shaderProgram)
currentShaderProgram = m_ComputePipelineStateDesc.shaderProgram->As<CShaderProgram>();
CShaderProgram* nextShaderProgram = nullptr;
if (desc.shaderProgram)
nextShaderProgram = desc.shaderProgram->As<CShaderProgram>();
if (nextShaderProgram)
nextShaderProgram->Bind(currentShaderProgram);
else if (currentShaderProgram)
currentShaderProgram->Unbind();
m_ShaderProgram = nextShaderProgram;
}
}
void CDeviceCommandContext::UploadTexture(
ITexture* texture, const Format format,
const void* data, const size_t dataSize,
const uint32_t level, const uint32_t layer)
{
UploadTextureRegion(texture, format, data, dataSize,
0, 0,
std::max(1u, texture->GetWidth() >> level),
std::max(1u, texture->GetHeight() >> level),
level, layer);
}
void CDeviceCommandContext::UploadTextureRegion(
ITexture* destinationTexture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t xOffset, const uint32_t yOffset,
const uint32_t width, const uint32_t height,
const uint32_t level, const uint32_t layer)
{
ENSURE(destinationTexture);
CTexture* texture = destinationTexture->As<CTexture>();
ENSURE(texture->GetUsage() & Renderer::Backend::ITexture::Usage::TRANSFER_DST);
ENSURE(width > 0 && height > 0);
if (texture->GetType() == CTexture::Type::TEXTURE_2D)
{
ENSURE(layer == 0);
if (texture->GetFormat() == Format::R8G8B8A8_UNORM ||
texture->GetFormat() == Format::R8G8B8_UNORM ||
#if !CONFIG2_GLES
texture->GetFormat() == Format::R8_UNORM ||
#endif
texture->GetFormat() == Format::A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
size_t bytesPerPixel = 4;
GLenum pixelFormat = GL_RGBA;
switch (dataFormat)
{
case Format::R8G8B8A8_UNORM: [[fallthrough]];
case Format::R8G8B8A8_SRGB:
break;
case Format::R8G8B8_UNORM:
pixelFormat = GL_RGB;
bytesPerPixel = 3;
break;
#if !CONFIG2_GLES
case Format::R8_UNORM:
pixelFormat = GL_RED;
bytesPerPixel = 1;
break;
#endif
case Format::A8_UNORM:
pixelFormat = GL_ALPHA;
bytesPerPixel = 1;
break;
case Format::L8_UNORM:
pixelFormat = GL_LUMINANCE;
bytesPerPixel = 1;
break;
default:
debug_warn("Unexpected format.");
break;
}
ENSURE(dataSize == width * height * bytesPerPixel);
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, level,
xOffset, yOffset, width, height,
pixelFormat, GL_UNSIGNED_BYTE, data);
}
else if (
texture->GetFormat() == Format::BC1_RGB_UNORM ||
texture->GetFormat() == Format::BC1_RGB_SRGB ||
texture->GetFormat() == Format::BC1_RGBA_UNORM ||
texture->GetFormat() == Format::BC1_RGBA_SRGB ||
texture->GetFormat() == Format::BC2_UNORM ||
texture->GetFormat() == Format::BC2_SRGB ||
texture->GetFormat() == Format::BC3_UNORM ||
texture->GetFormat() == Format::BC3_SRGB)
{
ENSURE(xOffset == 0 && yOffset == 0);
ENSURE(texture->GetFormat() == dataFormat);
// TODO: add data size check.
GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
switch (texture->GetFormat())
{
case Format::BC1_RGB_SRGB:
internalFormat = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
break;
case Format::BC1_RGBA_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
case Format::BC1_RGBA_SRGB:
internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
break;
case Format::BC2_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case Format::BC2_SRGB:
internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
break;
case Format::BC3_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
case Format::BC3_SRGB:
internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
break;
default:
break;
}
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glCompressedTexImage2D(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data);
}
else
debug_warn("Unsupported format");
}
else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE)
{
if (texture->GetFormat() == Format::R8G8B8A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
ENSURE(level == 0 && layer < 6);
ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height);
const size_t bpp = 4;
ENSURE(dataSize == width * height * bpp);
// The order of layers should be the following:
// front, back, top, bottom, right, left
static const GLenum targets[6] =
{
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle());
glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
else
debug_warn("Unsupported format");
}
else
debug_warn("Unsupported type");
}
void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize)
{
ENSURE(!m_InsideFramebufferPass);
UploadBufferRegion(buffer, data, 0, dataSize);
}
void CDeviceCommandContext::UploadBuffer(
IBuffer* buffer, const UploadBufferFunction& uploadFunction)
{
ENSURE(!m_InsideFramebufferPass);
UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction);
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(data);
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
ScopedBufferBind scopedBufferBind(this, buffer->As<CBuffer>());
// Uniform buffers is a relatively new feature so we don't need to use a
// dynamic upload.
if (buffer->IsDynamic() && buffer->GetType() != IBuffer::Type::UNIFORM)
{
UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, [data, dataSize](u8* mappedData)
{
std::memcpy(mappedData, data, dataSize);
});
}
else
{
glBufferSubData(target, dataOffset, dataSize, data);
}
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
ScopedBufferBind scopedBufferBind(this, buffer->As<CBuffer>());
ENSURE(buffer->IsDynamic());
UploadDynamicBufferRegionImpl(target, buffer->GetSize(), dataOffset, dataSize, uploadFunction);
}
void CDeviceCommandContext::InsertTimestampQuery(const uint32_t handle, const bool /*isScopeBegin*/)
{
// GL can have the only one command context so we can call commands on
// the deivce side.
m_Device->InsertTimestampQuery(handle);
}
void CDeviceCommandContext::BeginScopedLabel(const char* name)
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
++m_ScopedLabelDepth;
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name);
}
void CDeviceCommandContext::EndScopedLabel()
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
ENSURE(m_ScopedLabelDepth > 0);
--m_ScopedLabelDepth;
glPopDebugGroup();
}
void CDeviceCommandContext::BindTexture(
const uint32_t unit, const GLenum target, const GLuint handle)
{
ENSURE(unit < m_BoundTextures.size());
#if CONFIG2_GLES
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP);
#else
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE);
#endif
if (m_ActiveTextureUnit != unit)
{
glActiveTexture(GL_TEXTURE0 + unit);
m_ActiveTextureUnit = unit;
}
if (m_BoundTextures[unit].target == target && m_BoundTextures[unit].handle == handle)
return;
if (m_BoundTextures[unit].target != target && m_BoundTextures[unit].target && m_BoundTextures[unit].handle)
glBindTexture(m_BoundTextures[unit].target, 0);
if (m_BoundTextures[unit].handle != handle)
glBindTexture(target, handle);
m_BoundTextures[unit] = {target, handle};
}
void CDeviceCommandContext::BindBuffer(const IBuffer::Type type, CBuffer* buffer)
{
ENSURE(!buffer || buffer->GetType() == type);
if (type == IBuffer::Type::VERTEX)
{
if (m_VertexBuffer == buffer)
return;
m_VertexBuffer = buffer;
}
else if (type == IBuffer::Type::INDEX)
{
if (!buffer)
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
}
const GLenum target = BufferTypeToGLTarget(type);
const GLuint handle = buffer ? buffer->GetHandle() : 0;
glBindBuffer(target, handle);
const size_t cacheIndex = static_cast<size_t>(type);
ENSURE(cacheIndex < m_BoundBuffers.size());
m_BoundBuffers[cacheIndex].second = handle;
}
void CDeviceCommandContext::OnTextureDestroy(CTexture* texture)
{
ENSURE(texture);
for (size_t index = 0; index < m_BoundTextures.size(); ++index)
if (m_BoundTextures[index].handle == texture->GetHandle())
BindTexture(index, GL_TEXTURE_2D, 0);
}
void CDeviceCommandContext::Flush()
{
ENSURE(m_ScopedLabelDepth == 0);
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
GPU_SCOPED_LABEL(this, "CDeviceCommandContext::Flush");
ResetStates();
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
for (size_t unit = 0; unit < m_BoundTextures.size(); ++unit)
{
if (m_BoundTextures[unit].handle)
BindTexture(unit, GL_TEXTURE_2D, 0);
}
BindBuffer(CBuffer::Type::INDEX, nullptr);
BindBuffer(CBuffer::Type::VERTEX, nullptr);
}
void CDeviceCommandContext::ResetStates()
{
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true);
SetScissors(0, nullptr);
m_Framebuffer = nullptr;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
void CDeviceCommandContext::SetGraphicsPipelineStateImpl(
const SGraphicsPipelineStateDesc& pipelineStateDesc, const bool force)
{
ENSURE(!m_InsidePass);
if (m_GraphicsPipelineStateDesc.shaderProgram != pipelineStateDesc.shaderProgram)
{
CShaderProgram* currentShaderProgram = nullptr;
if (m_GraphicsPipelineStateDesc.shaderProgram)
{
currentShaderProgram =
static_cast<CShaderProgram*>(m_GraphicsPipelineStateDesc.shaderProgram);
}
CShaderProgram* nextShaderProgram = nullptr;
if (pipelineStateDesc.shaderProgram)
{
nextShaderProgram =
static_cast<CShaderProgram*>(pipelineStateDesc.shaderProgram);
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
const VertexAttributeStream stream = static_cast<VertexAttributeStream>(index);
m_VertexAttributeFormat[index].active = nextShaderProgram->IsStreamActive(stream);
m_VertexAttributeFormat[index].initialized = false;
m_VertexAttributeFormat[index].bindingSlot = std::numeric_limits<uint32_t>::max();
}
}
if (nextShaderProgram)
nextShaderProgram->Bind(currentShaderProgram);
else if (currentShaderProgram)
currentShaderProgram->Unbind();
m_ShaderProgram = nextShaderProgram;
}
const SDepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState;
const SDepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState;
if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled)
{
if (nextDepthStencilStateDesc.depthTestEnabled)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
}
if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp)
{
glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp));
}
if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled)
{
ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled);
}
if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled)
{
if (nextDepthStencilStateDesc.stencilTestEnabled)
glEnable(GL_STENCIL_TEST);
else
glDisable(GL_STENCIL_TEST);
}
if (force ||
currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace ||
currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOp(
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
else
{
if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace)
{
glStencilOpSeparate(
GL_FRONT,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOpSeparate(
GL_BACK,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp));
}
}
}
if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask)
{
ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask);
}
if (force ||
currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference ||
currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask ||
currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp ||
currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
glStencilFunc(
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
else
{
glStencilFuncSeparate(GL_FRONT,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
glStencilFuncSeparate(GL_BACK,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
}
const SBlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState;
const SBlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState;
if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled)
{
if (nextBlendStateDesc.enabled)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
}
if (force ||
currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor ||
currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor ||
currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor ||
currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor)
{
if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor &&
nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor)
{
glBlendFunc(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor));
}
else
{
glBlendFuncSeparate(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor));
}
}
if (force ||
currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp ||
currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp)
{
if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp)
{
glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp));
}
else
{
glBlendEquationSeparate(
Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp),
Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp));
}
}
if (force ||
currentBlendStateDesc.constant != nextBlendStateDesc.constant)
{
glBlendColor(
nextBlendStateDesc.constant.r,
nextBlendStateDesc.constant.g,
nextBlendStateDesc.constant.b,
nextBlendStateDesc.constant.a);
}
if (force ||
currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask)
{
ApplyColorMask(nextBlendStateDesc.colorWriteMask);
}
const SRasterizationStateDesc& currentRasterizationStateDesc =
m_GraphicsPipelineStateDesc.rasterizationState;
const SRasterizationStateDesc& nextRasterizationStateDesc =
pipelineStateDesc.rasterizationState;
if (force ||
currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode)
{
#if !CONFIG2_GLES
glPolygonMode(
GL_FRONT_AND_BACK,
nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL);
#endif
}
if (force ||
currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode)
{
if (nextRasterizationStateDesc.cullMode == CullMode::NONE)
{
glDisable(GL_CULL_FACE);
}
else
{
if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE)
glEnable(GL_CULL_FACE);
glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK);
}
}
if (force ||
currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace)
{
if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE)
glFrontFace(GL_CW);
else
glFrontFace(GL_CCW);
}
#if !CONFIG2_GLES
if (force ||
currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled)
{
if (nextRasterizationStateDesc.depthBiasEnabled)
glEnable(GL_POLYGON_OFFSET_FILL);
else
glDisable(GL_POLYGON_OFFSET_FILL);
}
if (force ||
currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor ||
currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor)
{
glPolygonOffset(
nextRasterizationStateDesc.depthBiasSlopeFactor,
nextRasterizationStateDesc.depthBiasConstantFactor);
}
#endif
m_GraphicsPipelineStateDesc = pipelineStateDesc;
}
void CDeviceCommandContext::BlitFramebuffer(
IFramebuffer* srcFramebuffer, IFramebuffer* dstFramebuffer,
[[maybe_unused]] const Rect& sourceRegion, [[maybe_unused]] const Rect& destinationRegion,
[[maybe_unused]] const Sampler::Filter filter)
{
ENSURE(!m_InsideFramebufferPass);
[[maybe_unused]] CFramebuffer* destinationFramebuffer = dstFramebuffer->As<CFramebuffer>();
[[maybe_unused]] CFramebuffer* sourceFramebuffer = srcFramebuffer->As<CFramebuffer>();
#if CONFIG2_GLES
debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES");
#else
// Source framebuffer should not be backbuffer.
ENSURE(sourceFramebuffer->GetHandle() != 0);
ENSURE(destinationFramebuffer != sourceFramebuffer);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
// TODO: add more check for internal formats.
glBlitFramebufferEXT(
sourceRegion.x, sourceRegion.y, sourceRegion.width, sourceRegion.height,
destinationRegion.x, destinationRegion.y, destinationRegion.width, destinationRegion.height,
(sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()),
filter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST);
#endif
}
void CDeviceCommandContext::ResolveFramebuffer(
IFramebuffer* srcFramebuffer, IFramebuffer* dstFramebuffer)
{
ENSURE(!m_InsideFramebufferPass);
CFramebuffer* destinationFramebuffer = dstFramebuffer->As<CFramebuffer>();
CFramebuffer* sourceFramebuffer = srcFramebuffer->As<CFramebuffer>();
ENSURE(destinationFramebuffer->GetWidth() == sourceFramebuffer->GetWidth());
ENSURE(destinationFramebuffer->GetHeight() == sourceFramebuffer->GetHeight());
#if CONFIG2_GLES
debug_warn("CDeviceCommandContext::ResolveFramebuffer is not implemented for GLES");
#else
// Source framebuffer should not be backbuffer.
ENSURE(sourceFramebuffer->GetHandle() != 0);
ENSURE(destinationFramebuffer != sourceFramebuffer);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
glBlitFramebufferEXT(
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
(sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()),
GL_NEAREST);
#endif
}
void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil)
{
ENSURE(m_InsideFramebufferPass);
const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0;
const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0;
const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0;
GLbitfield mask = 0;
if (needsColor)
{
ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA);
glClearColor(
m_Framebuffer->GetClearColor().r,
m_Framebuffer->GetClearColor().g,
m_Framebuffer->GetClearColor().b,
m_Framebuffer->GetClearColor().a);
mask |= GL_COLOR_BUFFER_BIT;
}
if (needsDepth)
{
ApplyDepthMask(true);
mask |= GL_DEPTH_BUFFER_BIT;
}
if (needsStencil)
{
ApplyStencilMask(std::numeric_limits<uint32_t>::max());
mask |= GL_STENCIL_BUFFER_BIT;
}
glClear(mask);
if (needsColor)
ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask);
if (needsDepth)
ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled);
if (needsStencil)
ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask);
}
void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer)
{
SetGraphicsPipelineStateImpl(
MakeDefaultGraphicsPipelineStateDesc(), false);
ENSURE(!m_InsideFramebufferPass);
m_InsideFramebufferPass = true;
ENSURE(framebuffer);
m_Framebuffer = framebuffer->As<CFramebuffer>();
ENSURE(m_Framebuffer->GetHandle() == 0 || (m_Framebuffer->GetWidth() > 0 && m_Framebuffer->GetHeight() > 0));
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_Framebuffer->GetHandle());
if (m_Device->UseFramebufferInvalidating())
{
InvalidateFramebuffer(
m_Framebuffer,
m_Framebuffer->GetColorAttachmentLoadOp() != AttachmentLoadOp::LOAD,
m_Framebuffer->GetDepthStencilAttachmentLoadOp() != AttachmentLoadOp::LOAD);
}
const bool needsClearColor =
m_Framebuffer->GetColorAttachmentLoadOp() == AttachmentLoadOp::CLEAR;
const bool needsClearDepthStencil =
m_Framebuffer->GetDepthStencilAttachmentLoadOp() == AttachmentLoadOp::CLEAR;
if (needsClearColor || needsClearDepthStencil)
{
ClearFramebuffer(
needsClearColor, needsClearDepthStencil, needsClearDepthStencil);
}
}
void CDeviceCommandContext::EndFramebufferPass()
{
if (m_Device->UseFramebufferInvalidating())
{
InvalidateFramebuffer(
m_Framebuffer,
m_Framebuffer->GetColorAttachmentStoreOp() != AttachmentStoreOp::STORE,
m_Framebuffer->GetDepthStencilAttachmentStoreOp() != AttachmentStoreOp::STORE);
}
ENSURE(m_InsideFramebufferPass);
m_InsideFramebufferPass = false;
if (m_Framebuffer->GetHandle() != 0)
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
m_Framebuffer = nullptr;
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), false);
}
void CDeviceCommandContext::ReadbackFramebufferSync(
ISwapChain&, const uint32_t x, const uint32_t y,
const uint32_t width, const uint32_t height,
void* data)
{
glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
}
void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors)
{
ENSURE(scissorCount <= 1);
if (scissorCount == 0)
{
if (m_ScissorCount != scissorCount)
glDisable(GL_SCISSOR_TEST);
}
else
{
if (m_ScissorCount != scissorCount)
glEnable(GL_SCISSOR_TEST);
ENSURE(scissors);
if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0])
{
m_Scissors[0] = scissors[0];
glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height);
}
}
m_ScissorCount = scissorCount;
}
void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports)
{
ENSURE(m_InsideFramebufferPass);
ENSURE(viewportCount == 1);
glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height);
}
void CDeviceCommandContext::SetVertexInputLayout(
IVertexInputLayout* vertexInputLayout)
{
ENSURE(vertexInputLayout);
for (const SVertexAttributeFormat& attribute : vertexInputLayout->As<CVertexInputLayout>()->GetAttributes())
{
const uint32_t index = static_cast<uint32_t>(attribute.stream);
ENSURE(index < m_VertexAttributeFormat.size());
ENSURE(attribute.bindingSlot < m_VertexAttributeFormat.size());
if (!m_VertexAttributeFormat[index].active)
continue;
m_VertexAttributeFormat[index].format = attribute.format;
m_VertexAttributeFormat[index].offset = attribute.offset;
m_VertexAttributeFormat[index].stride = attribute.stride;
m_VertexAttributeFormat[index].rate = attribute.rate;
m_VertexAttributeFormat[index].bindingSlot = attribute.bindingSlot;
m_VertexAttributeFormat[index].initialized = true;
}
}
void CDeviceCommandContext::SetVertexBuffer(
const uint32_t bindingSlot, IBuffer* buffer, const uint32_t offset)
{
ENSURE(buffer);
ENSURE(buffer->GetType() == IBuffer::Type::VERTEX);
ENSURE(m_ShaderProgram);
BindBuffer(buffer->GetType(), buffer->As<CBuffer>());
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot)
continue;
ENSURE(m_VertexAttributeFormat[index].initialized);
const VertexAttributeStream stream = static_cast<VertexAttributeStream>(index);
m_ShaderProgram->VertexAttribPointer(stream,
m_VertexAttributeFormat[index].format,
m_VertexAttributeFormat[index].offset + offset,
m_VertexAttributeFormat[index].stride,
m_VertexAttributeFormat[index].rate,
nullptr);
}
}
void CDeviceCommandContext::SetVertexBufferData(
const uint32_t bindingSlot, const void* data, const uint32_t dataSize)
{
ENSURE(data);
ENSURE(m_ShaderProgram);
ENSURE(dataSize > 0);
BindBuffer(CBuffer::Type::VERTEX, nullptr);
for (size_t index = 0; index < m_VertexAttributeFormat.size(); ++index)
{
if (!m_VertexAttributeFormat[index].active || m_VertexAttributeFormat[index].bindingSlot != bindingSlot)
continue;
ENSURE(m_VertexAttributeFormat[index].initialized);
const VertexAttributeStream stream = static_cast<VertexAttributeStream>(index);
// We don't know how many vertices will be used in a draw command, so we
// assume at least one vertex.
ENSURE(dataSize >= m_VertexAttributeFormat[index].offset + m_VertexAttributeFormat[index].stride);
m_ShaderProgram->VertexAttribPointer(stream,
m_VertexAttributeFormat[index].format,
m_VertexAttributeFormat[index].offset,
m_VertexAttributeFormat[index].stride,
m_VertexAttributeFormat[index].rate,
data);
}
}
void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer)
{
ENSURE(buffer->GetType() == CBuffer::Type::INDEX);
m_IndexBuffer = buffer->As<CBuffer>();
m_IndexBufferData = nullptr;
BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer);
}
void CDeviceCommandContext::SetIndexBufferData(const void* data, const uint32_t dataSize)
{
ENSURE(dataSize > 0);
if (m_IndexBuffer)
{
BindBuffer(CBuffer::Type::INDEX, nullptr);
m_IndexBuffer = nullptr;
}
m_IndexBufferData = data;
}
void CDeviceCommandContext::BeginPass()
{
ENSURE(!m_InsidePass);
m_InsidePass = true;
}
void CDeviceCommandContext::EndPass()
{
ENSURE(m_InsidePass);
m_InsidePass = false;
}
void CDeviceCommandContext::Draw(
const uint32_t firstVertex, const uint32_t vertexCount)
{
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
// Some drivers apparently don't like count = 0 in glDrawArrays here, so skip
// all drawing in that case.
if (vertexCount == 0)
return;
m_ShaderProgram->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount);
}
void CDeviceCommandContext::DrawIndexed(
const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset)
{
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
ENSURE(vertexOffset == 0);
if (m_IndexBuffer)
{
ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize());
}
m_ShaderProgram->AssertPointersBound();
// Don't use glMultiDrawElements here since it doesn't have a significant
// performance impact and it suffers from various driver bugs (e.g. it breaks
// in Mesa 7.10 swrast with index VBOs).
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT,
static_cast<const void*>((static_cast<const uint8_t*>(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)));
}
void CDeviceCommandContext::DrawInstanced(
[[maybe_unused]] const uint32_t firstVertex, const uint32_t vertexCount,
const uint32_t firstInstance, const uint32_t instanceCount)
{
ENSURE(m_Device->GetCapabilities().instancing);
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
if (vertexCount == 0 || instanceCount == 0)
return;
ENSURE(firstInstance == 0);
m_ShaderProgram->AssertPointersBound();
#if CONFIG2_GLES
ENSURE(!m_Device->GetCapabilities().instancing);
#else
glDrawArraysInstancedARB(GL_TRIANGLES, firstVertex, vertexCount, instanceCount);
#endif
}
void CDeviceCommandContext::DrawIndexedInstanced(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t firstInstance, [[maybe_unused]] const uint32_t instanceCount,
const int32_t vertexOffset)
{
ENSURE(m_Device->GetCapabilities().instancing);
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
ENSURE(m_IndexBuffer || m_IndexBufferData);
if (indexCount == 0)
return;
ENSURE(firstInstance == 0 && vertexOffset == 0);
if (m_IndexBuffer)
{
ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize());
}
m_ShaderProgram->AssertPointersBound();
// Don't use glMultiDrawElements here since it doesn't have a significant
// performance impact and it suffers from various driver bugs (e.g. it breaks
// in Mesa 7.10 swrast with index VBOs).
#if CONFIG2_GLES
ENSURE(!m_Device->GetCapabilities().instancing);
#else
glDrawElementsInstancedARB(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT,
static_cast<const void*>((static_cast<const uint8_t*>(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)),
instanceCount);
#endif
}
void CDeviceCommandContext::DrawIndexedInRange(
const uint32_t firstIndex, const uint32_t indexCount,
[[maybe_unused]] const uint32_t start, [[maybe_unused]] const uint32_t end)
{
ENSURE(m_ShaderProgram);
ENSURE(m_InsidePass);
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
const void* indices =
static_cast<const void*>((static_cast<const uint8_t*>(m_IndexBufferData) + sizeof(uint16_t) * firstIndex));
m_ShaderProgram->AssertPointersBound();
// Draw with DrawRangeElements where available, since it might be more
// efficient for slow hardware.
#if CONFIG2_GLES
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);
#else
glDrawRangeElements(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices);
#endif
}
void CDeviceCommandContext::BeginComputePass()
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
m_InsideComputePass = true;
}
void CDeviceCommandContext::EndComputePass()
{
ENSURE(m_InsideComputePass);
m_InsideComputePass = false;
}
void CDeviceCommandContext::Dispatch(
[[maybe_unused]] const uint32_t groupCountX,
[[maybe_unused]] const uint32_t groupCountY,
[[maybe_unused]] const uint32_t groupCountZ)
{
#if !CONFIG2_GLES
ENSURE(m_InsideComputePass);
glDispatchCompute(groupCountX, groupCountY, groupCountZ);
// Storage buffers should be managed explicitly by InsertMemoryBarrier.
if (m_ShaderProgram->HasImageUniforms())
{
// TODO: we might want to do binding tracking to avoid redundant barriers.
glMemoryBarrier(
GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT );
}
#endif
}
void CDeviceCommandContext::InsertMemoryBarrier(
const uint32_t /*srcStageMask*/, [[maybe_unused]] const uint32_t dstStageMask,
[[maybe_unused]] const uint32_t srcAccessMask, [[maybe_unused]] const uint32_t dstAccessMask)
{
#if !CONFIG2_GLES
ENSURE(!m_InsideFramebufferPass);
GLbitfield barriers{0};
if (srcAccessMask & Access::SHADER_WRITE)
{
if (dstStageMask & PipelineStage::VERTEX_INPUT)
{
if (dstAccessMask & Access::VERTEX_ATTRIBUTE_READ)
barriers |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
if (dstAccessMask & Access::INDEX_READ)
barriers |= GL_ELEMENT_ARRAY_BARRIER_BIT;
}
if (dstStageMask & (PipelineStage::VERTEX_SHADER | PipelineStage::FRAGMENT_SHADER | PipelineStage::COMPUTE_SHADER))
{
if (dstAccessMask & (Access::SHADER_READ | Access::SHADER_WRITE))
barriers |= GL_SHADER_STORAGE_BARRIER_BIT;
if (dstAccessMask & Access::UNIFORM_READ)
barriers |= GL_UNIFORM_BARRIER_BIT;
}
}
if (barriers)
glMemoryBarrier(barriers);
#endif
}
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
{
ENSURE(m_ShaderProgram);
ENSURE(texture);
ENSURE(texture->GetUsage() & Renderer::Backend::ITexture::Usage::SAMPLED);
const CShaderProgram::TextureUnit textureUnit =
m_ShaderProgram->GetTextureUnit(bindingSlot);
if (!textureUnit.type)
return;
if (textureUnit.type != GL_SAMPLER_2D &&
#if !CONFIG2_GLES
textureUnit.type != GL_SAMPLER_2D_SHADOW &&
#endif
textureUnit.type != GL_SAMPLER_CUBE)
{
LOGERROR("CDeviceCommandContext::SetTexture: expected sampler at binding slot");
return;
}
#if !CONFIG2_GLES
if (textureUnit.type == GL_SAMPLER_2D_SHADOW)
{
if (!IsDepthFormat(texture->GetFormat()))
{
LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture type (expected depth texture)");
return;
}
}
#endif
ENSURE(textureUnit.unit >= 0);
const uint32_t unit = textureUnit.unit;
if (unit >= m_BoundTextures.size())
{
LOGERROR("CDeviceCommandContext::SetTexture: Invalid texture unit (too big)");
return;
}
BindTexture(unit, textureUnit.target, texture->As<CTexture>()->GetHandle());
}
void CDeviceCommandContext::SetStorageTexture([[maybe_unused]] const int32_t bindingSlot,
[[maybe_unused]] ITexture* texture)
{
#if !CONFIG2_GLES
ENSURE(m_ShaderProgram);
ENSURE(texture);
ENSURE(texture->GetUsage() & Renderer::Backend::ITexture::Usage::STORAGE);
const CShaderProgram::TextureUnit textureUnit =
m_ShaderProgram->GetTextureUnit(bindingSlot);
if (!textureUnit.type)
return;
ENSURE(textureUnit.type == GL_IMAGE_2D);
ENSURE(texture->GetFormat() == Format::R8G8B8A8_UNORM);
glBindImageTexture(textureUnit.unit, texture->As<CTexture>()->GetHandle(), 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);
#endif
}
void CDeviceCommandContext::SetStorageBuffer([[maybe_unused]] const int32_t bindingSlot,
[[maybe_unused]] IBuffer* buffer)
{
#if !CONFIG2_GLES
if (bindingSlot < 0)
return;
ENSURE(m_ShaderProgram);
ENSURE(buffer);
ENSURE(buffer->GetUsage() & Renderer::Backend::IBuffer::Usage::STORAGE);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, m_ShaderProgram->GetStorageBuffer(bindingSlot), buffer->As<CBuffer>()->GetHandle());
#endif
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float value)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, value);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ, const float valueW)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot, std::span<const float> values)
{
ENSURE(m_ShaderProgram);
m_ShaderProgram->SetUniform(bindingSlot, values);
}
CDeviceCommandContext::ScopedBind::ScopedBind(
CDeviceCommandContext* deviceCommandContext,
const GLenum target, const GLuint handle)
: m_DeviceCommandContext(deviceCommandContext),
m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit]),
m_ActiveTextureUnit(deviceCommandContext->m_ActiveTextureUnit)
{
const uint32_t unit = m_DeviceCommandContext->m_BoundTextures.size() - 1;
m_DeviceCommandContext->BindTexture(unit, target, handle);
}
CDeviceCommandContext::ScopedBind::~ScopedBind()
{
m_DeviceCommandContext->BindTexture(
m_ActiveTextureUnit, m_OldBindUnit.target, m_OldBindUnit.handle);
}
CDeviceCommandContext::ScopedBufferBind::ScopedBufferBind(
CDeviceCommandContext* deviceCommandContext, CBuffer* buffer)
: m_DeviceCommandContext(deviceCommandContext)
{
ENSURE(buffer);
m_CacheIndex = static_cast<size_t>(buffer->GetType());
ENSURE(m_CacheIndex < m_DeviceCommandContext->m_BoundBuffers.size());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
const GLuint handle = buffer->GetHandle();
if (m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first == target &&
m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second == handle)
{
// Use an invalid index as a sign that we don't need to restore the
// bound buffer.
m_CacheIndex = m_DeviceCommandContext->m_BoundBuffers.size();
}
else
{
glBindBuffer(target, handle);
}
}
CDeviceCommandContext::ScopedBufferBind::~ScopedBufferBind()
{
if (m_CacheIndex >= m_DeviceCommandContext->m_BoundBuffers.size())
return;
glBindBuffer(
m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].first,
m_DeviceCommandContext->m_BoundBuffers[m_CacheIndex].second);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer