Files
filament/filament/backend/src/opengl/OpenGLDriver.cpp
T
Mathias Agopian ea5d7d4f47 Add a way to retrieve the GL_RENDERER and GL_VENDOR strings
The API works this way:

```
auto vendor = OpenGLPlatform::getVendorString(engine->getDriver());
```

Obviously the Backend must be `Backend::OPENGL` and the `Driver` must 
have been created with an `OpenGLPlatform` subclass.


FIXES=[385367277]
2025-02-05 15:31:39 -08:00

4152 lines
155 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "OpenGLDriver.h"
#include "CommandStreamDispatcher.h"
#include "GLTexture.h"
#include "GLUtils.h"
#include "OpenGLContext.h"
#include "OpenGLDriverFactory.h"
#include "OpenGLProgram.h"
#include "OpenGLTimerQuery.h"
#include "SystraceProfile.h"
#include "gl_headers.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/BufferDescriptor.h>
#include <backend/CallbackHandler.h>
#include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/PipelineState.h>
#include <backend/Platform.h>
#include <backend/Program.h>
#include <backend/TargetBufferInfo.h>
#include "private/backend/Dispatcher.h"
#include "private/backend/DriverApi.h"
#include <type_traits>
#include <utils/BitmaskEnum.h>
#include <utils/FixedCapacityVector.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/Systrace.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <algorithm>
#include <chrono>
#include <functional>
#include <limits>
#include <memory>
#include <mutex>
#include <new>
#include <type_traits>
#include <utility>
#include <variant>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#if defined(__EMSCRIPTEN__)
#include <emscripten.h>
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
#endif
// We can only support this feature on OpenGL ES 3.1+
// Support is currently disabled as we don't need it
#define TEXTURE_2D_MULTISAMPLE_SUPPORTED false
#if defined(__EMSCRIPTEN__)
#define HAS_MAPBUFFERS 0
#else
#define HAS_MAPBUFFERS 1
#endif
#define DEBUG_GROUP_MARKER_NONE 0x00 // no debug marker
#define DEBUG_GROUP_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_GROUP_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_GROUP_MARKER_ALL 0xFF // all markers
#define DEBUG_MARKER_NONE 0x00 // no debug marker
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_MARKER_PROFILE 0x04 // profiling on the backend side (systrace)
#define DEBUG_MARKER_ALL (0xFF & ~DEBUG_MARKER_PROFILE) // all markers
// set to the desired debug marker level (for user markers [default: All])
#define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_ALL
// set to the desired debug level (for internal debugging [Default: None])
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
// Override the debug markers if we are forcing profiling mode
#if defined(FILAMENT_FORCE_PROFILING_MODE)
# undef DEBUG_GROUP_MARKER_LEVEL
# undef DEBUG_MARKER_LEVEL
# define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_NONE
# define DEBUG_MARKER_LEVEL DEBUG_MARKER_PROFILE
#endif
#if DEBUG_MARKER_LEVEL == DEBUG_MARKER_PROFILE
# define DEBUG_MARKER()
# define PROFILE_MARKER(marker) PROFILE_SCOPE(marker);
# if DEBUG_GROUP_MARKER_LEVEL != DEBUG_GROUP_MARKER_NONE
# error PROFILING is exclusive; group markers must be disabled.
# endif
# ifndef NDEBUG
# error PROFILING is meaningless in DEBUG mode.
# endif
#elif DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
# define DEBUG_MARKER() DebugMarker _debug_marker(*this, __func__);
# define PROFILE_MARKER(marker) DEBUG_MARKER()
# if DEBUG_MARKER_LEVEL & DEBUG_MARKER_PROFILE
# error PROFILING is exclusive; all other debug features must be disabled.
# endif
#else
# define DEBUG_MARKER()
# define PROFILE_MARKER(marker)
#endif
using namespace filament::math;
using namespace utils;
namespace filament::backend {
Driver* OpenGLDriverFactory::create(
OpenGLPlatform* const platform,
void* const sharedGLContext,
const Platform::DriverConfig& driverConfig) noexcept {
return OpenGLDriver::create(platform, sharedGLContext, driverConfig);
}
using namespace GLUtils;
// ------------------------------------------------------------------------------------------------
UTILS_NOINLINE
OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* const platform,
void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
assert_invariant(platform);
OpenGLPlatform* const ec = platform;
#if 0
// this is useful for development, but too verbose even for debug builds
// For reference on a 64-bits machine in Release mode:
// GLIndexBuffer : 8 moderate
// GLSwapChain : 16 few
// GLTimerQuery : 16 few
// GLFence : 24 few
// GLRenderPrimitive : 32 many
// GLBufferObject : 32 many
// -- less than or equal 32 bytes
// GLTexture : 64 moderate
// GLVertexBuffer : 76 moderate
// OpenGLProgram : 96 moderate
// -- less than or equal 96 bytes
// GLStream : 104 few
// GLRenderTarget : 112 few
// GLVertexBufferInfo : 132 moderate
// -- less than or equal to 136 bytes
slog.d
<< "\nGLSwapChain: " << sizeof(GLSwapChain)
<< "\nGLBufferObject: " << sizeof(GLBufferObject)
<< "\nGLVertexBuffer: " << sizeof(GLVertexBuffer)
<< "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo)
<< "\nGLIndexBuffer: " << sizeof(GLIndexBuffer)
<< "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive)
<< "\nGLTexture: " << sizeof(GLTexture)
<< "\nGLTimerQuery: " << sizeof(GLTimerQuery)
<< "\nGLStream: " << sizeof(GLStream)
<< "\nGLRenderTarget: " << sizeof(GLRenderTarget)
<< "\nGLFence: " << sizeof(GLFence)
<< "\nOpenGLProgram: " << sizeof(OpenGLProgram)
<< io::endl;
#endif
// here we check we're on a supported version of GL before initializing the driver
GLint major = 0, minor = 0;
bool const success = OpenGLContext::queryOpenGLVersion(&major, &minor);
if (UTILS_UNLIKELY(!success)) {
PANIC_LOG("Can't get OpenGL version");
cleanup:
ec->terminate();
return {};
}
#if defined(BACKEND_OPENGL_VERSION_GLES)
if (UTILS_UNLIKELY(!(major >= 2 && minor >= 0))) {
PANIC_LOG("OpenGL ES 2.0 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
major = 2;
minor = 0;
}
#else
// we require GL 4.1 headers and minimum version
if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
PANIC_LOG("OpenGL 4.1 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
#endif
size_t const defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
Platform::DriverConfig validConfig{ driverConfig };
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
OpenGLDriver* const driver = new(std::nothrow) OpenGLDriver(ec, validConfig);
return driver;
}
// ------------------------------------------------------------------------------------------------
OpenGLDriver::DebugMarker::DebugMarker(OpenGLDriver& driver, const char* string) noexcept
: driver(driver) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
if (UTILS_LIKELY(driver.getContext().ext.EXT_debug_marker)) {
glPushGroupMarkerEXT(GLsizei(strlen(string)), string);
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
#endif
#endif
}
OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
if (UTILS_LIKELY(driver.getContext().ext.EXT_debug_marker)) {
glPopGroupMarkerEXT();
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
#endif
#endif
}
// ------------------------------------------------------------------------------------------------
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
mContext(mPlatform, driverConfig),
mShaderCompilerService(*this),
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck),
mDriverConfig(driverConfig),
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
// set a reasonable default value for our stream array
mTexturesWithStreamsAttached.reserve(8);
mStreamsWithPendingAcquiredImage.reserve(8);
#ifndef NDEBUG
slog.i << "OS version: " << mPlatform.getOSVersion() << io::endl;
#endif
// Timer queries are core in GL 3.3, otherwise we need EXT_disjoint_timer_query
// iOS headers don't define GL_EXT_disjoint_timer_query, so make absolutely sure
// we won't use it.
#if defined(BACKEND_OPENGL_VERSION_GL)
assert_invariant(mContext.ext.EXT_disjoint_timer_query);
#endif
mShaderCompilerService.init();
}
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
// this is called from the main thread. Can't call GL.
}
Dispatcher OpenGLDriver::getDispatcher() const noexcept {
auto dispatcher = ConcreteDispatcher<OpenGLDriver>::make();
if (mContext.isES2()) {
dispatcher.draw2_ = +[](Driver& driver, CommandBase* base, intptr_t* next){
using Cmd = COMMAND_TYPE(draw2);
OpenGLDriver& concreteDriver = static_cast<OpenGLDriver&>(driver);
Cmd::execute(&OpenGLDriver::draw2GLES2, concreteDriver, base, next);
};
}
return dispatcher;
}
// ------------------------------------------------------------------------------------------------
// Driver interface concrete implementation
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::terminate() {
// wait for the GPU to finish executing all commands
glFinish();
mShaderCompilerService.terminate();
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
// and make sure to execute all the GpuCommandCompleteOps callbacks
executeGpuCommandsCompleteOps();
// as well as the FrameCompleteOps callbacks
if (UTILS_UNLIKELY(!mFrameCompleteOps.empty())) {
for (auto&& op: mFrameCompleteOps) {
op();
}
mFrameCompleteOps.clear();
}
// because we called glFinish(), all callbacks should have been executed
assert_invariant(mGpuCommandCompleteOps.empty());
#endif
delete mCurrentPushConstants;
mCurrentPushConstants = nullptr;
mContext.terminate();
mPlatform.terminate();
}
ShaderModel OpenGLDriver::getShaderModel() const noexcept {
return mContext.getShaderModel();
}
ShaderLanguage OpenGLDriver::getShaderLanguage() const noexcept {
return mContext.isES2() ? ShaderLanguage::ESSL1 : ShaderLanguage::ESSL3;
}
// ------------------------------------------------------------------------------------------------
// Change and track GL state
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::resetState(int) {
mContext.resetState();
}
void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
mContext.bindSampler(unit, sampler);
}
void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!mValidProgram)) {
return;
}
#endif
utils::Slice<std::pair<GLint, ConstantType>> constants;
if (stage == ShaderStage::VERTEX) {
constants = mCurrentPushConstants->vertexConstants;
} else if (stage == ShaderStage::FRAGMENT) {
constants = mCurrentPushConstants->fragmentConstants;
}
assert_invariant(index < constants.size());
auto const& [location, type] = constants[index];
// This push constant wasn't found in the shader. It's ok to return without error-ing here.
if (location < 0) {
return;
}
if (std::holds_alternative<bool>(value)) {
assert_invariant(type == ConstantType::BOOL);
bool const bval = std::get<bool>(value);
glUniform1i(location, bval ? 1 : 0);
} else if (std::holds_alternative<float>(value)) {
assert_invariant(type == ConstantType::FLOAT);
float const fval = std::get<float>(value);
glUniform1f(location, fval);
} else {
assert_invariant(type == ConstantType::INT);
int const ival = std::get<int>(value);
glUniform1i(location, ival);
}
}
void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
assert_invariant(t != nullptr);
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
}
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
bool success = true;
if (mBoundProgram != p) {
// compile/link the program if needed and call glUseProgram
success = p->use(this, mContext);
assert_invariant(success == p->isValid());
if (success) {
// TODO: we could even improve this if the program could tell us which of the descriptors
// bindings actually changed. In practice, it is likely that set 0 or 1 might not
// change often.
decltype(mInvalidDescriptorSetBindings) changed;
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
mInvalidDescriptorSetBindings |= changed;
mBoundProgram = p;
}
}
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
// Set the output colorspace for this program (linear or rec709). This is only relevant
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
p->setRec709ColorSpace(mRec709OutputColorspace);
}
return success;
}
void OpenGLDriver::setRasterState(RasterState rs) noexcept {
auto& gl = mContext;
mRenderPassColorWrite |= rs.colorWrite;
mRenderPassDepthWrite |= rs.depthWrite;
// culling state
if (rs.culling == CullingMode::NONE) {
gl.disable(GL_CULL_FACE);
} else {
gl.enable(GL_CULL_FACE);
gl.cullFace(getCullingMode(rs.culling));
}
gl.frontFace(rs.inverseFrontFaces ? GL_CW : GL_CCW);
// blending state
if (!rs.hasBlending()) {
gl.disable(GL_BLEND);
} else {
gl.enable(GL_BLEND);
gl.blendEquation(
getBlendEquationMode(rs.blendEquationRGB),
getBlendEquationMode(rs.blendEquationAlpha));
gl.blendFunction(
getBlendFunctionMode(rs.blendFunctionSrcRGB),
getBlendFunctionMode(rs.blendFunctionSrcAlpha),
getBlendFunctionMode(rs.blendFunctionDstRGB),
getBlendFunctionMode(rs.blendFunctionDstAlpha));
}
// depth test
if (rs.depthFunc == RasterState::DepthFunc::A && !rs.depthWrite) {
gl.disable(GL_DEPTH_TEST);
} else {
gl.enable(GL_DEPTH_TEST);
gl.depthFunc(getDepthFunc(rs.depthFunc));
gl.depthMask(GLboolean(rs.depthWrite));
}
// write masks
gl.colorMask(GLboolean(rs.colorWrite));
// AA
if (rs.alphaToCoverage) {
gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
} else {
gl.disable(GL_SAMPLE_ALPHA_TO_COVERAGE);
}
if (gl.ext.EXT_depth_clamp) {
if (rs.depthClamp) {
gl.enable(GL_DEPTH_CLAMP);
} else {
gl.disable(GL_DEPTH_CLAMP);
}
}
}
void OpenGLDriver::setStencilState(StencilState ss) noexcept {
auto& gl = mContext;
mRenderPassStencilWrite |= ss.stencilWrite;
// stencil test / operation
// GL_STENCIL_TEST must be enabled if we're testing OR writing to the stencil buffer.
if (UTILS_LIKELY(
ss.front.stencilFunc == StencilState::StencilFunction::A &&
ss.back.stencilFunc == StencilState::StencilFunction::A &&
ss.front.stencilOpDepthFail == StencilOperation::KEEP &&
ss.back.stencilOpDepthFail == StencilOperation::KEEP &&
ss.front.stencilOpStencilFail == StencilOperation::KEEP &&
ss.back.stencilOpStencilFail == StencilOperation::KEEP &&
ss.front.stencilOpDepthStencilPass == StencilOperation::KEEP &&
ss.back.stencilOpDepthStencilPass == StencilOperation::KEEP)) {
// that's equivalent to having the stencil test disabled
gl.disable(GL_STENCIL_TEST);
} else {
gl.enable(GL_STENCIL_TEST);
}
// glStencilFuncSeparate() also sets the reference value, which may be used depending
// on the stencilOp, so we always need to call glStencilFuncSeparate().
gl.stencilFuncSeparate(
getStencilFunc(ss.front.stencilFunc), ss.front.ref, ss.front.readMask,
getStencilFunc(ss.back.stencilFunc), ss.back.ref, ss.back.readMask);
if (UTILS_LIKELY(!ss.stencilWrite)) {
gl.stencilMaskSeparate(0x00, 0x00);
} else {
// Stencil ops are only relevant when stencil write is enabled
gl.stencilOpSeparate(
getStencilOp(ss.front.stencilOpStencilFail),
getStencilOp(ss.front.stencilOpDepthFail),
getStencilOp(ss.front.stencilOpDepthStencilPass),
getStencilOp(ss.back.stencilOpStencilFail),
getStencilOp(ss.back.stencilOpDepthFail),
getStencilOp(ss.back.stencilOpDepthStencilPass));
gl.stencilMaskSeparate(ss.front.writeMask, ss.back.writeMask);
}
}
// ------------------------------------------------------------------------------------------------
// Creating driver objects
// ------------------------------------------------------------------------------------------------
Handle<HwVertexBufferInfo> OpenGLDriver::createVertexBufferInfoS() noexcept {
return initHandle<GLVertexBufferInfo>();
}
Handle<HwVertexBuffer> OpenGLDriver::createVertexBufferS() noexcept {
return initHandle<GLVertexBuffer>();
}
Handle<HwIndexBuffer> OpenGLDriver::createIndexBufferS() noexcept {
return initHandle<GLIndexBuffer>();
}
Handle<HwBufferObject> OpenGLDriver::createBufferObjectS() noexcept {
return initHandle<GLBufferObject>();
}
Handle<HwRenderPrimitive> OpenGLDriver::createRenderPrimitiveS() noexcept {
return initHandle<GLRenderPrimitive>();
}
Handle<HwProgram> OpenGLDriver::createProgramS() noexcept {
return initHandle<OpenGLProgram>();
}
Handle<HwTexture> OpenGLDriver::createTextureS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewSwizzleS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureExternalImage2S() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureExternalImageS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureExternalImagePlaneS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::importTextureS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwRenderTarget> OpenGLDriver::createDefaultRenderTargetS() noexcept {
return initHandle<GLRenderTarget>();
}
Handle<HwRenderTarget> OpenGLDriver::createRenderTargetS() noexcept {
return initHandle<GLRenderTarget>();
}
Handle<HwFence> OpenGLDriver::createFenceS() noexcept {
return initHandle<GLFence>();
}
Handle<HwSwapChain> OpenGLDriver::createSwapChainS() noexcept {
return initHandle<GLSwapChain>();
}
Handle<HwSwapChain> OpenGLDriver::createSwapChainHeadlessS() noexcept {
return initHandle<GLSwapChain>();
}
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
return initHandle<GLTimerQuery>();
}
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
return initHandle<GLDescriptorSetLayout>();
}
Handle<HwDescriptorSet> OpenGLDriver::createDescriptorSetS() noexcept {
return initHandle<GLDescriptorSet>();
}
void OpenGLDriver::createVertexBufferInfoR(
Handle<HwVertexBufferInfo> vbih,
uint8_t bufferCount,
uint8_t attributeCount,
AttributeArray attributes) {
DEBUG_MARKER()
construct<GLVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
}
void OpenGLDriver::createVertexBufferR(
Handle<HwVertexBuffer> vbh,
uint32_t vertexCount,
Handle<HwVertexBufferInfo> vbih) {
DEBUG_MARKER()
construct<GLVertexBuffer>(vbh, vertexCount, vbih);
}
void OpenGLDriver::createIndexBufferR(
Handle<HwIndexBuffer> ibh,
ElementType elementType,
uint32_t indexCount,
BufferUsage usage) {
DEBUG_MARKER()
auto& gl = mContext;
uint8_t const elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
GLIndexBuffer* ib = construct<GLIndexBuffer>(ibh, elementSize, indexCount);
glGenBuffers(1, &ib->gl.buffer);
GLsizeiptr const size = elementSize * indexCount;
gl.bindVertexArray(nullptr);
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, nullptr, getBufferUsage(usage));
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh,
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage) {
DEBUG_MARKER()
assert_invariant(byteCount > 0);
auto& gl = mContext;
if (bindingType == BufferObjectBinding::VERTEX) {
gl.bindVertexArray(nullptr);
}
GLBufferObject* bo = construct<GLBufferObject>(boh, byteCount, bindingType, usage);
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
bo->gl.id = ++mLastAssignedEmulatedUboId;
bo->gl.buffer = malloc(byteCount);
memset(bo->gl.buffer, 0, byteCount);
} else {
bo->gl.binding = GLUtils::getBufferBindingType(bindingType);
glGenBuffers(1, &bo->gl.id);
gl.bindBuffer(bo->gl.binding, bo->gl.id);
glBufferData(bo->gl.binding, byteCount, nullptr, getBufferUsage(usage));
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType pt) {
DEBUG_MARKER()
auto& gl = mContext;
GLIndexBuffer const* const ib = handle_cast<const GLIndexBuffer*>(ibh);
assert_invariant(ib->elementSize == 2 || ib->elementSize == 4);
GLVertexBuffer* const vb = handle_cast<GLVertexBuffer*>(vbh);
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
rp->gl.indicesShift = (ib->elementSize == 4u) ? 2u : 1u;
rp->gl.indicesType = (ib->elementSize == 4u) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
rp->gl.vertexBufferWithObjects = vbh;
rp->type = pt;
rp->vbih = vb->vbih;
// create a name for this VAO in the current context
gl.procs.genVertexArrays(1, &rp->gl.vao[gl.contextIndex]);
// this implies our name is up-to-date
rp->gl.nameVersion = gl.state.age;
// binding the VAO will actually create it
gl.bindVertexArray(&rp->gl);
// Note: we don't update the vertex buffer bindings in the VAO just yet, we will do that
// later in draw() or bindRenderPrimitive(). At this point, the HwVertexBuffer might not
// have all its buffers set.
// this records the index buffer into the currently bound VAO
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
DEBUG_MARKER()
if (UTILS_UNLIKELY(mContext.isES2())) {
// Here we patch the specialization constants to enable or not the rec709 output
// color space emulation in this program. Obviously, the backend shouldn't know about
// specific spec-constants, so we need to handle failures gracefully. This cannot be
// done at Material creation time because only the backend has access to
// Platform.isSRGBSwapChainSupported().
if (!mPlatform.isSRGBSwapChainSupported()) {
auto& specializationConstants = program.getSpecializationConstants();
auto pos = std::find_if(specializationConstants.begin(), specializationConstants.end(),
[](auto&& sc) {
// This constant must match
// ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION
// which we can't use here because it's defined in EngineEnums.h.
// (we're breaking layering here, but it's for the good cause).
return sc.id == 3;
});
if (pos != specializationConstants.end()) {
pos->value = true;
}
}
}
construct<OpenGLProgram>(ph, *this, std::move(program));
CHECK_GL_ERROR(utils::slog.e)
}
UTILS_NOINLINE
void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept {
auto& gl = mContext;
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
#ifdef GL_EXT_protected_textures
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_UNLIKELY(useProtectedMemory)) {
assert_invariant(gl.ext.EXT_protected_textures);
glTexParameteri(t->gl.target, GL_TEXTURE_PROTECTED_EXT, 1);
}
#endif
#endif
switch (t->gl.target) {
case GL_TEXTURE_2D:
case GL_TEXTURE_CUBE_MAP:
if (UTILS_LIKELY(!gl.isES2())) {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glTexStorage2D(t->gl.target, GLsizei(t->levels), t->gl.internalFormat,
GLsizei(width), GLsizei(height));
#endif
}
#ifdef BACKEND_OPENGL_VERSION_GLES
else {
// FIXME: handle compressed texture format
auto [format, type] = textureFormatToFormatAndType(t->format);
assert_invariant(format != GL_NONE && type != GL_NONE);
for (GLint level = 0 ; level < t->levels ; level++ ) {
if (t->gl.target == GL_TEXTURE_CUBE_MAP) {
for (GLint face = 0 ; face < 6 ; face++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
level, GLint(t->gl.internalFormat),
GLsizei(width), GLsizei(height), 0,
format, type, nullptr);
}
} else {
glTexImage2D(t->gl.target, level, GLint(t->gl.internalFormat),
std::max(GLsizei(1), GLsizei(width >> level)),
std::max(GLsizei(1), GLsizei(height >> level)),
0, format, type, nullptr);
}
}
}
#endif
break;
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY: {
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glTexStorage3D(t->gl.target, GLsizei(t->levels), t->gl.internalFormat,
GLsizei(width), GLsizei(height), GLsizei(depth));
#endif
break;
}
case GL_TEXTURE_CUBE_MAP_ARRAY: {
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glTexStorage3D(t->gl.target, GLsizei(t->levels), t->gl.internalFormat,
GLsizei(width), GLsizei(height), GLsizei(depth) * 6);
#endif
break;
}
#ifdef BACKEND_OPENGL_LEVEL_GLES31
case GL_TEXTURE_2D_MULTISAMPLE:
if constexpr (TEXTURE_2D_MULTISAMPLE_SUPPORTED) {
// NOTE: if there is a mix of texture and renderbuffers, "fixed_sample_locations" must be true
// NOTE: what's the benefit of setting "fixed_sample_locations" to false?
if (mContext.isAtLeastGL<4, 3>() || mContext.isAtLeastGLES<3, 1>()) {
// only supported from GL 4.3 and GLES 3.1 headers
glTexStorage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
GLsizei(width), GLsizei(height), GL_TRUE);
}
#ifdef BACKEND_OPENGL_VERSION_GL
else {
// only supported in GL (GL4.1 doesn't support glTexStorage2DMultisample)
glTexImage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
GLsizei(width), GLsizei(height), GL_TRUE);
}
#endif
} else {
PANIC_LOG("GL_TEXTURE_2D_MULTISAMPLE is not supported");
}
break;
#endif // BACKEND_OPENGL_LEVEL_GLES31
default: // cannot happen
break;
}
// textureStorage can be used to reallocate the texture at a new size
t->width = width;
t->height = height;
t->depth = depth;
}
void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
DEBUG_MARKER()
GLenum internalFormat = getInternalFormat(format);
assert_invariant(internalFormat);
if (any(usage & TextureUsage::PROTECTED)) {
// renderbuffers don't have a protected mode, so we need to use a texture
// because protected textures are only supported on GLES 3.2, MSAA will be available.
usage |= TextureUsage::SAMPLEABLE;
} else if (any(usage & TextureUsage::UPLOADABLE)) {
// if we have the uploadable flag, we also need to use a texture
usage |= TextureUsage::SAMPLEABLE;
} else if (target != SamplerType::SAMPLER_2D) {
// renderbuffers can only be 2D
usage |= TextureUsage::SAMPLEABLE;
} else if (levels > 1) {
// renderbuffers can't have mip levels
usage |= TextureUsage::SAMPLEABLE;
}
auto& gl = mContext;
samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples));
GLTexture* t = construct<GLTexture>(th, target, levels, samples, w, h, depth, format, usage);
if (UTILS_LIKELY(usage & TextureUsage::SAMPLEABLE)) {
if (UTILS_UNLIKELY(gl.isES2())) {
// on ES2, format and internal format must match
// FIXME: handle compressed texture format
internalFormat = textureFormatToFormatAndType(format).first;
}
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
t->externalTexture = mPlatform.createExternalImageTexture();
if (t->externalTexture) {
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
// internalFormat actually depends on the external image, but it doesn't matter
// because it's not used anywhere for anything important.
t->gl.internalFormat = internalFormat;
t->gl.baseLevel = 0;
t->gl.maxLevel = 0;
}
} else {
glGenTextures(1, &t->gl.id);
t->gl.internalFormat = internalFormat;
t->gl.target = getTextureTargetNotExternal(target);
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
#endif
}
textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED));
}
} else {
t->gl.internalFormat = internalFormat;
t->gl.target = GL_RENDERBUFFER;
glGenRenderbuffers(1, &t->gl.id);
renderBufferStorage(t->gl.id, internalFormat, w, h, samples);
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
Handle<HwTexture> srch, uint8_t baseLevel, uint8_t levelCount) {
DEBUG_MARKER()
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
<< "TextureView can only be created on a SAMPLEABLE texture";
FILAMENT_CHECK_PRECONDITION(!src->gl.imported)
<< "TextureView can't be created on imported textures";
if (!src->ref) {
// lazily create the ref handle, because most textures will never get a texture view
src->ref = initHandle<GLTextureRef>();
}
GLTexture* t = construct<GLTexture>(th,
src->target,
src->levels,
src->samples,
src->width, src->height, src->depth,
src->format,
src->usage);
t->gl = src->gl;
t->gl.sidecarRenderBufferMS = 0;
t->gl.sidecarSamples = 1;
auto srcBaseLevel = src->gl.baseLevel;
auto srcMaxLevel = src->gl.maxLevel;
if (srcBaseLevel > srcMaxLevel) {
srcBaseLevel = 0;
srcMaxLevel = 127;
}
t->gl.baseLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel);
t->gl.maxLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel + levelCount - 1);
// increase reference count to this texture handle
t->ref = src->ref;
GLTextureRef* ref = handle_cast<GLTextureRef*>(t->ref);
assert_invariant(ref);
ref->count++;
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
DEBUG_MARKER()
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
<< "TextureView can only be created on a SAMPLEABLE texture";
FILAMENT_CHECK_PRECONDITION(!src->gl.imported)
<< "TextureView can't be created on imported textures";
if (!src->ref) {
// lazily create the ref handle, because most textures will never get a texture view
src->ref = initHandle<GLTextureRef>();
}
GLTexture* t = construct<GLTexture>(th,
src->target,
src->levels,
src->samples,
src->width, src->height, src->depth,
src->format,
src->usage);
t->gl = src->gl;
t->gl.baseLevel = src->gl.baseLevel;
t->gl.maxLevel = src->gl.maxLevel;
t->gl.sidecarRenderBufferMS = 0;
t->gl.sidecarSamples = 1;
auto getChannel = [&swizzle = src->gl.swizzle](TextureSwizzle ch) {
switch (ch) {
case TextureSwizzle::SUBSTITUTE_ZERO:
case TextureSwizzle::SUBSTITUTE_ONE:
return ch;
case TextureSwizzle::CHANNEL_0:
return swizzle[0];
case TextureSwizzle::CHANNEL_1:
return swizzle[1];
case TextureSwizzle::CHANNEL_2:
return swizzle[2];
case TextureSwizzle::CHANNEL_3:
return swizzle[3];
}
};
t->gl.swizzle = {
getChannel(r),
getChannel(g),
getChannel(b),
getChannel(a),
};
// increase reference count to this texture handle
t->ref = src->ref;
GLTextureRef* ref = handle_cast<GLTextureRef*>(t->ref);
assert_invariant(ref);
ref->count++;
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureExternalImage2R(Handle<HwTexture> th, SamplerType target,
TextureFormat format, uint32_t width, uint32_t height, TextureUsage usage,
Platform::ExternalImageHandleRef image) {
DEBUG_MARKER()
usage |= TextureUsage::SAMPLEABLE;
usage &= ~TextureUsage::UPLOADABLE;
auto& gl = mContext;
GLenum internalFormat = getInternalFormat(format);
if (UTILS_UNLIKELY(gl.isES2())) {
// on ES2, format and internal format must match
// FIXME: handle compressed texture format
internalFormat = textureFormatToFormatAndType(format).first;
}
assert_invariant(internalFormat);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage);
assert_invariant(t);
t->externalTexture = mPlatform.createExternalImageTexture();
if (t->externalTexture) {
if (target == SamplerType::SAMPLER_EXTERNAL) {
if (UTILS_LIKELY(gl.ext.OES_EGL_image_external_essl3)) {
t->externalTexture->target = GL_TEXTURE_EXTERNAL_OES;
} else {
// revert to texture 2D if external is not supported; what else can we do?
t->externalTexture->target = GL_TEXTURE_2D;
}
} else {
t->externalTexture->target = getTextureTargetNotExternal(target);
}
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
// internalFormat actually depends on the external image, but it doesn't matter
// because it's not used anywhere for anything important.
t->gl.internalFormat = internalFormat;
t->gl.baseLevel = 0;
t->gl.maxLevel = 0;
t->gl.external = true; // forces bindTexture() call (they're never cached)
}
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
if (mPlatform.setExternalImage(image, t->externalTexture)) {
// the target and id can be reset each time
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
}
}
void OpenGLDriver::createTextureExternalImageR(Handle<HwTexture> th, SamplerType target,
TextureFormat format, uint32_t width, uint32_t height, TextureUsage usage, void* image) {
DEBUG_MARKER()
usage |= TextureUsage::SAMPLEABLE;
usage &= ~TextureUsage::UPLOADABLE;
auto& gl = mContext;
GLenum internalFormat = getInternalFormat(format);
if (UTILS_UNLIKELY(gl.isES2())) {
// on ES2, format and internal format must match
// FIXME: handle compressed texture format
internalFormat = textureFormatToFormatAndType(format).first;
}
assert_invariant(internalFormat);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage);
assert_invariant(t);
t->externalTexture = mPlatform.createExternalImageTexture();
if (t->externalTexture) {
if (target == SamplerType::SAMPLER_EXTERNAL) {
if (UTILS_LIKELY(gl.ext.OES_EGL_image_external_essl3)) {
t->externalTexture->target = GL_TEXTURE_EXTERNAL_OES;
} else {
// revert to texture 2D if external is not supported; what else can we do?
t->externalTexture->target = GL_TEXTURE_2D;
}
} else {
t->externalTexture->target = getTextureTargetNotExternal(target);
}
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
// internalFormat actually depends on the external image, but it doesn't matter
// because it's not used anywhere for anything important.
t->gl.internalFormat = internalFormat;
t->gl.baseLevel = 0;
t->gl.maxLevel = 0;
t->gl.external = true; // forces bindTexture() call (they're never cached)
}
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
if (mPlatform.setExternalImage(image, t->externalTexture)) {
// the target and id can be reset each time
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
}
}
void OpenGLDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {
// not relevant for the OpenGL backend
}
void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) {
DEBUG_MARKER()
auto& gl = mContext;
samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples));
GLTexture* t = construct<GLTexture>(th, target, levels, samples, w, h, depth, format, usage);
t->gl.id = (GLuint)id;
t->gl.imported = true;
t->gl.internalFormat = getInternalFormat(format);
assert_invariant(t->gl.internalFormat);
switch (target) {
case SamplerType::SAMPLER_EXTERNAL:
t->gl.target = GL_TEXTURE_EXTERNAL_OES;
t->gl.external = true; // forces bindTexture() call (they're never cached)
break;
case SamplerType::SAMPLER_2D:
t->gl.target = GL_TEXTURE_2D;
break;
case SamplerType::SAMPLER_3D:
t->gl.target = GL_TEXTURE_3D;
break;
case SamplerType::SAMPLER_2D_ARRAY:
t->gl.target = GL_TEXTURE_2D_ARRAY;
break;
case SamplerType::SAMPLER_CUBEMAP:
t->gl.target = GL_TEXTURE_CUBE_MAP;
break;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY;
break;
}
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
#endif
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) {
// NOTE: this is called often and must be as efficient as possible.
auto& gl = mContext;
#ifndef NDEBUG
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
// The VAO for the given render primitive must already be bound.
GLint vaoBinding;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding);
assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]);
}
#endif
if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion &&
rp->gl.stateVersion == gl.state.age)) {
return;
}
GLVertexBufferInfo const* const vbi = handle_cast<const GLVertexBufferInfo*>(vb->vbih);
for (size_t i = 0, n = vbi->attributes.size(); i < n; i++) {
const auto& attribute = vbi->attributes[i];
const uint8_t bi = attribute.buffer;
if (bi != Attribute::BUFFER_UNUSED) {
// if a buffer is defined it must not be invalid.
assert_invariant(vb->gl.buffers[bi]);
// if we're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
assert_invariant(!(gl.isES2() && (attribute.flags & Attribute::FLAG_INTEGER_TARGET)));
gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]);
GLuint const index = i;
GLint const size = (GLint)getComponentCount(attribute.type);
GLenum const type = getComponentType(attribute.type);
GLboolean const normalized = getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED);
GLsizei const stride = attribute.stride;
void const* pointer = reinterpret_cast<void const *>(attribute.offset);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) {
// integer attributes can't be floats
assert_invariant(type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_SHORT ||
type == GL_UNSIGNED_SHORT || type == GL_INT || type == GL_UNSIGNED_INT);
glVertexAttribIPointer(index, size, type, stride, pointer);
} else
#endif
{
glVertexAttribPointer(index, size, type, normalized, stride, pointer);
}
gl.enableVertexAttribArray(&rp->gl, GLuint(i));
} else {
// In some OpenGL implementations, we must supply a properly-typed placeholder for
// every integer input that is declared in the vertex shader.
// Note that the corresponding doesn't have to be enabled and in fact won't be. If it
// was enabled, it would indicate a user-error (providing the wrong type of array).
// With a disabled array, the vertex shader gets the attribute from glVertexAttrib,
// and must have the proper intergerness.
// But at this point, we don't know what the shader requirements are, and so we must
// rely on the attribute.
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) {
if (!gl.isES2()) {
// on ES2, we know the shader doesn't have integer attributes
glVertexAttribI4ui(GLuint(i), 0, 0, 0, 0);
}
} else
#endif
{
glVertexAttrib4f(GLuint(i), 0, 0, 0, 0);
}
gl.disableVertexAttribArray(&rp->gl, GLuint(i));
}
}
rp->gl.stateVersion = gl.state.age;
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
rp->gl.vertexBufferVersion = vb->bufferObjectsVersion;
} else {
// if we don't have OES_vertex_array_object, we never update the buffer version so
// that it's always reset in draw
}
}
void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
GLRenderTarget const* rt, GLenum attachment, uint8_t layerCount) noexcept {
#if !defined(NDEBUG)
// Only used by assert_invariant() checks below
UTILS_UNUSED_IN_RELEASE auto valueForLevel = [](size_t level, size_t value) {
return std::max(size_t(1), value >> level);
};
#endif
GLTexture* t = handle_cast<GLTexture*>(binfo.handle);
assert_invariant(t);
assert_invariant(t->target != SamplerType::SAMPLER_EXTERNAL);
assert_invariant(rt->width <= valueForLevel(binfo.level, t->width) &&
rt->height <= valueForLevel(binfo.level, t->height));
// Declare a small mask of bits that will later be OR'd into the texture's resolve mask.
TargetBufferFlags resolveFlags = {};
switch (attachment) {
case GL_COLOR_ATTACHMENT0:
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
case GL_COLOR_ATTACHMENT1:
case GL_COLOR_ATTACHMENT2:
case GL_COLOR_ATTACHMENT3:
case GL_COLOR_ATTACHMENT4:
case GL_COLOR_ATTACHMENT5:
case GL_COLOR_ATTACHMENT6:
case GL_COLOR_ATTACHMENT7:
#endif
assert_invariant((attachment != GL_COLOR_ATTACHMENT0 && !mContext.isES2())
|| attachment == GL_COLOR_ATTACHMENT0);
static_assert(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT == 8);
resolveFlags = getTargetBufferFlagsAt(attachment - GL_COLOR_ATTACHMENT0);
break;
case GL_DEPTH_ATTACHMENT:
resolveFlags = TargetBufferFlags::DEPTH;
break;
case GL_STENCIL_ATTACHMENT:
resolveFlags = TargetBufferFlags::STENCIL;
break;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
case GL_DEPTH_STENCIL_ATTACHMENT:
assert_invariant(!mContext.isES2());
resolveFlags = TargetBufferFlags::DEPTH;
resolveFlags |= TargetBufferFlags::STENCIL;
break;
#endif
default:
break;
}
// depth/stencil attachments must match the rendertarget sample count
// because EXT_multisampled_render_to_texture[2] doesn't resolve the depth/stencil
// buffers:
// for EXT_multisampled_render_to_texture
// "the contents of the multisample buffer become undefined"
// for EXT_multisampled_render_to_texture2
// "the contents of the multisample buffer is discarded rather than resolved -
// equivalent to the application calling InvalidateFramebuffer for this attachment"
UTILS_UNUSED bool attachmentTypeNotSupportedByMSRTT = false;
switch (attachment) {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
case GL_DEPTH_STENCIL_ATTACHMENT:
assert_invariant(!mContext.isES2());
UTILS_FALLTHROUGH;
#endif
case GL_DEPTH_ATTACHMENT:
case GL_STENCIL_ATTACHMENT:
attachmentTypeNotSupportedByMSRTT = rt->gl.samples != t->samples;
break;
default:
break;
}
auto& gl = mContext;
GLenum target = GL_TEXTURE_2D;
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
switch (t->target) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_3D:
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
// this could be GL_TEXTURE_2D_MULTISAMPLE or GL_TEXTURE_2D_ARRAY
target = t->gl.target;
// note: multi-sampled textures can't have mipmaps
break;
case SamplerType::SAMPLER_CUBEMAP:
target = getCubemapTarget(binfo.layer);
// note: cubemaps can't be multi-sampled
break;
case SamplerType::SAMPLER_EXTERNAL:
// This is an error. We have asserted in debug build.
target = t->gl.target;
break;
}
}
// We can't use FramebufferTexture2DMultisampleEXT with GL_TEXTURE_2D_ARRAY or GL_TEXTURE_CUBE_MAP_ARRAY.
if (!(target == GL_TEXTURE_2D ||
target == GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
target == GL_TEXTURE_CUBE_MAP_NEGATIVE_X ||
target == GL_TEXTURE_CUBE_MAP_POSITIVE_Y ||
target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ||
target == GL_TEXTURE_CUBE_MAP_POSITIVE_Z ||
target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)) {
attachmentTypeNotSupportedByMSRTT = true;
}
if (rt->gl.samples <= 1 ||
(rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture)) {
// On GL3.2 / GLES3.1 and above multisample is handled when creating the texture.
// If multisampled textures are not supported and we end-up here, things should
// still work, albeit without MSAA.
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
switch (target) {
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
case GL_TEXTURE_2D:
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_TEXTURE_2D_MULTISAMPLE:
#endif
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment,
target, t->gl.id, binfo.level);
} else {
assert_invariant(target == GL_TEXTURE_2D);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
GL_RENDERBUFFER, t->gl.id);
}
break;
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP_ARRAY:
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
// TODO: support multiview for iOS and WebGL
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_IOS)
if (layerCount > 1) {
// if layerCount > 1, it means we use the multiview extension.
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment,
t->gl.id, 0, binfo.layer, layerCount);
} else
#endif // !defined(__EMSCRIPTEN__) && !defined(FILAMENT_IOS)
{
// GL_TEXTURE_2D_MULTISAMPLE_ARRAY is not supported in GLES
glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment,
t->gl.id, binfo.level, binfo.layer);
}
#endif
break;
default:
// we shouldn't be here
break;
}
CHECK_GL_ERROR(utils::slog.e)
} else
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_multisampled_render_to_texture
// EXT_multisampled_render_to_texture only support GL_COLOR_ATTACHMENT0
if (!attachmentTypeNotSupportedByMSRTT && (t->depth <= 1)
&& ((gl.ext.EXT_multisampled_render_to_texture && attachment == GL_COLOR_ATTACHMENT0)
|| gl.ext.EXT_multisampled_render_to_texture2)) {
assert_invariant(rt->gl.samples > 1);
// We have a multi-sample rendertarget and we have EXT_multisampled_render_to_texture,
// so, we can directly use a 1-sample texture as attachment, multi-sample resolve,
// will happen automagically and efficiently in the driver.
// This extension only exists on OpenGL ES.
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
attachment, target, t->gl.id, binfo.level, rt->gl.samples);
} else {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
GL_RENDERBUFFER, t->gl.id);
}
CHECK_GL_ERROR(utils::slog.e)
} else
#endif // GL_EXT_multisampled_render_to_texture
#endif // __EMSCRIPTEN__
if (!any(t->usage & TextureUsage::SAMPLEABLE) && t->samples > 1) {
assert_invariant(rt->gl.samples > 1);
assert_invariant(glIsRenderbuffer(t->gl.id));
// Since this attachment is not sampleable, there is no need for a sidecar or explicit
// resolve. We can simply render directly into the renderbuffer that was allocated in
// createTexture.
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, t->gl.id);
// Clear the resolve bit for this particular attachment. Note that other attachment(s)
// might be sampleable, so this does not necessarily prevent the resolve from occurring.
resolveFlags = TargetBufferFlags::NONE;
} else {
// Here we emulate EXT_multisampled_render_to_texture.
//
// This attachment needs to be explicitly resolved in endRenderPass().
// The first step is to create a sidecar multi-sampled renderbuffer, which is where drawing
// will actually take place, and use that in lieu of the requested attachment.
// The sidecar will be destroyed when the render target handle is destroyed.
assert_invariant(rt->gl.samples > 1);
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
if (UTILS_UNLIKELY(t->gl.sidecarRenderBufferMS == 0 ||
rt->gl.samples != t->gl.sidecarSamples))
{
if (t->gl.sidecarRenderBufferMS == 0) {
glGenRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
renderBufferStorage(t->gl.sidecarRenderBufferMS,
t->gl.internalFormat, t->width, t->height, rt->gl.samples);
t->gl.sidecarSamples = rt->gl.samples;
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER,
t->gl.sidecarRenderBufferMS);
// Here we lazily create a "read" sidecar FBO, used later as the resolve target. Note that
// at least one of the render target's attachments needs to be both MSAA and sampleable in
// order for fbo_read to be created. If we never bother to create it, then endRenderPass()
// will skip doing an explicit resolve.
if (!rt->gl.fbo_read) {
glGenFramebuffers(1, &rt->gl.fbo_read);
}
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo_read);
switch (target) {
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
case GL_TEXTURE_2D:
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment,
target, t->gl.id, binfo.level);
} else {
assert_invariant(target == GL_TEXTURE_2D);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
GL_RENDERBUFFER, t->gl.id);
}
break;
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP_ARRAY:
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment,
t->gl.id, binfo.level, binfo.layer);
#endif
break;
default:
// we shouldn't be here
break;
}
CHECK_GL_ERROR(utils::slog.e)
}
rt->gl.resolve |= resolveFlags;
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
}
void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width, // NOLINT(readability-convert-member-functions-to-static)
uint32_t height, uint8_t samples) const noexcept {
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
if (samples > 1) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_multisampled_render_to_texture
auto& gl = mContext;
if (gl.ext.EXT_multisampled_render_to_texture ||
gl.ext.EXT_multisampled_render_to_texture2) {
glext::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER,
samples, internalformat, width, height);
} else
#endif // GL_EXT_multisampled_render_to_texture
#endif // __EMSCRIPTEN__
{
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
samples, internalformat, (GLsizei)width, (GLsizei)height);
#endif
}
} else {
glRenderbufferStorage(GL_RENDERBUFFER, internalformat, (GLsizei)width, (GLsizei)height);
}
// unbind the renderbuffer, to avoid any later confusion
glBindRenderbuffer(GL_RENDERBUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createDefaultRenderTargetR(
Handle<HwRenderTarget> rth, int) {
DEBUG_MARKER()
construct<GLRenderTarget>(rth, 0, 0); // FIXME: we don't know the width/height
GLRenderTarget* rt = handle_cast<GLRenderTarget*>(rth);
rt->gl.isDefault = true;
rt->gl.fbo = 0; // the actual id is resolved at binding time
rt->gl.samples = 1;
// FIXME: these flags should reflect the actual attachments present
rt->targets = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH;
}
void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
TargetBufferFlags targets,
uint32_t width,
uint32_t height,
uint8_t samples,
uint8_t layerCount,
MRT color,
TargetBufferInfo depth,
TargetBufferInfo stencil) {
DEBUG_MARKER()
GLRenderTarget* rt = construct<GLRenderTarget>(rth, width, height);
glGenFramebuffers(1, &rt->gl.fbo);
/*
* The GLES 3.0 spec states:
*
* --------------
*
* GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is returned
* - if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers or,
* - if the attached images are a mix of renderbuffers and textures,
* the value of GL_RENDERBUFFER_SAMPLES is not zero.
*
* GLES 3.1 (spec, refpages are wrong) and EXT_multisampled_render_to_texture add:
*
* The value of RENDERBUFFER_SAMPLES is the same for all
* attached renderbuffers; the value of TEXTURE_SAMPLES
* is the same for all texture attachments; and, if the attached
* images are a mix of renderbuffers and textures, the value of
* RENDERBUFFER_SAMPLES matches the value of TEXTURE_SAMPLES.
*
*
* In other words, heterogeneous (renderbuffer/textures) attachments are not supported in
* GLES3.0, unless EXT_multisampled_render_to_texture is present.
*
* 'features.multisample_texture' below is a proxy for "GLES3.1 or GL4.x".
*
* --------------
*
* About the size of the attachments:
*
* If the attachment sizes are not all identical, the results of rendering are defined only
* within the largest area that can fit in all the attachments. This area is defined as
* the intersection of rectangles having a lower left of (0, 0) and an upper right of
* (width, height) for each attachment. Contents of attachments outside this area are
* undefined after execution of a rendering command.
*/
samples = std::clamp(samples, uint8_t(1u), uint8_t(mContext.gets.max_samples));
rt->gl.samples = samples;
rt->targets = targets;
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = { std::numeric_limits<uint32_t>::max() };
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = { 0 };
auto checkDimensions = [&tmin, &tmax](GLTexture* t, uint8_t level) {
const auto twidth = std::max(1u, t->width >> level);
const auto theight = std::max(1u, t->height >> level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
};
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (any(targets & TargetBufferFlags::COLOR_ALL)) {
GLenum bufs[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { GL_NONE };
const size_t maxDrawBuffers = getMaxDrawBuffers();
for (size_t i = 0; i < maxDrawBuffers; i++) {
if (any(targets & getTargetBufferFlagsAt(i))) {
assert_invariant(color[i].handle);
rt->gl.color[i] = handle_cast<GLTexture*>(color[i].handle);
framebufferTexture(color[i], rt, GL_COLOR_ATTACHMENT0 + i, layerCount);
bufs[i] = GL_COLOR_ATTACHMENT0 + i;
checkDimensions(rt->gl.color[i], color[i].level);
}
}
if (UTILS_LIKELY(!getContext().isES2())) {
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
}
CHECK_GL_ERROR(utils::slog.e)
}
#endif
// handle special cases first (where depth/stencil are packed)
bool specialCased = false;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!getContext().isES2() &&
(targets & TargetBufferFlags::DEPTH_AND_STENCIL) == TargetBufferFlags::DEPTH_AND_STENCIL) {
assert_invariant(depth.handle);
// either we supplied only the depth handle or both depth/stencil are identical and not null
if (depth.handle && (stencil.handle == depth.handle || !stencil.handle)) {
rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
framebufferTexture(depth, rt, GL_DEPTH_STENCIL_ATTACHMENT, layerCount);
specialCased = true;
checkDimensions(rt->gl.depth, depth.level);
}
}
#endif
if (!specialCased) {
if (any(targets & TargetBufferFlags::DEPTH)) {
assert_invariant(depth.handle);
rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
framebufferTexture(depth, rt, GL_DEPTH_ATTACHMENT, layerCount);
checkDimensions(rt->gl.depth, depth.level);
}
if (any(targets & TargetBufferFlags::STENCIL)) {
assert_invariant(stencil.handle);
rt->gl.stencil = handle_cast<GLTexture*>(stencil.handle);
framebufferTexture(stencil, rt, GL_STENCIL_ATTACHMENT, layerCount);
checkDimensions(rt->gl.stencil, stencil.level);
}
}
// Verify that all attachments have the same dimensions.
assert_invariant(any(targets & TargetBufferFlags::ALL));
assert_invariant(tmin == tmax);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createFenceR(Handle<HwFence> fh, int) {
DEBUG_MARKER()
GLFence* f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
assert_invariant(mPlatform.canCreateFence());
f->fence = mPlatform.createFence();
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
else {
std::weak_ptr<GLFence::State> const weak = f->state;
whenGpuCommandsComplete([weak](){
auto state = weak.lock();
if (state) {
std::lock_guard const lock(state->lock);
state->status = FenceStatus::CONDITION_SATISFIED;
state->cond.notify_all();
}
});
}
#endif
}
void OpenGLDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
DEBUG_MARKER()
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
sc->swapChain = mPlatform.createSwapChain(nativeWindow, flags);
#if !defined(__EMSCRIPTEN__)
// note: in practice this should never happen on Android
FILAMENT_CHECK_POSTCONDITION(sc->swapChain) << "createSwapChain(" << nativeWindow << ", "
<< flags << ") failed. See logs for details.";
#endif
// See if we need the emulated rec709 output conversion
if (UTILS_UNLIKELY(mContext.isES2())) {
sc->rec709 = ((flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) &&
!mPlatform.isSRGBSwapChainSupported());
}
}
void OpenGLDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
uint32_t width, uint32_t height, uint64_t flags) {
DEBUG_MARKER()
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
sc->swapChain = mPlatform.createSwapChain(width, height, flags);
#if !defined(__EMSCRIPTEN__)
// note: in practice this should never happen on Android
FILAMENT_CHECK_POSTCONDITION(sc->swapChain)
<< "createSwapChainHeadless(" << width << ", " << height << ", " << flags
<< ") failed. See logs for details.";
#endif
// See if we need the emulated rec709 output conversion
if (UTILS_UNLIKELY(mContext.isES2())) {
sc->rec709 = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE &&
!mPlatform.isSRGBSwapChainSupported());
}
}
void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
DEBUG_MARKER()
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
mContext.createTimerQuery(tq);
}
void OpenGLDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
DescriptorSetLayout&& info) {
DEBUG_MARKER()
construct<GLDescriptorSetLayout>(dslh, std::move(info));
}
void OpenGLDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {
DEBUG_MARKER()
GLDescriptorSetLayout const* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
construct<GLDescriptorSet>(dsh, mContext, dslh, dsl);
}
// ------------------------------------------------------------------------------------------------
// Destroying driver objects
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
DEBUG_MARKER()
if (vbih) {
GLVertexBufferInfo const* vbi = handle_cast<const GLVertexBufferInfo*>(vbih);
destruct(vbih, vbi);
}
}
void OpenGLDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
DEBUG_MARKER()
if (vbh) {
GLVertexBuffer const* vb = handle_cast<const GLVertexBuffer*>(vbh);
destruct(vbh, vb);
}
}
void OpenGLDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
DEBUG_MARKER()
if (ibh) {
auto& gl = mContext;
GLIndexBuffer const* ib = handle_cast<const GLIndexBuffer*>(ibh);
gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
destruct(ibh, ib);
}
}
void OpenGLDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
DEBUG_MARKER()
if (boh) {
auto& gl = mContext;
GLBufferObject const* bo = handle_cast<const GLBufferObject*>(boh);
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
free(bo->gl.buffer);
} else {
gl.deleteBuffer(bo->gl.id, bo->gl.binding);
}
destruct(boh, bo);
}
}
void OpenGLDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
DEBUG_MARKER()
if (rph) {
auto& gl = mContext;
GLRenderPrimitive const* rp = handle_cast<const GLRenderPrimitive*>(rph);
gl.deleteVertexArray(rp->gl.vao[gl.contextIndex]);
// If we have a name in the "other" context, we need to schedule the destroy for
// later, because it can't be done here. VAOs are "container objects" and are not
// shared between contexts.
size_t const otherContextIndex = 1 - gl.contextIndex;
GLuint const nameInOtherContext = rp->gl.vao[otherContextIndex];
if (UTILS_UNLIKELY(nameInOtherContext)) {
gl.destroyWithContext(otherContextIndex,
[name = nameInOtherContext](OpenGLContext& gl) {
gl.deleteVertexArray(name);
});
}
destruct(rph, rp);
}
}
void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
DEBUG_MARKER()
if (ph) {
OpenGLProgram* p = handle_cast<OpenGLProgram*>(ph);
destruct(ph, p);
}
}
void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
DEBUG_MARKER()
if (th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture*>(th);
if (UTILS_LIKELY(!t->gl.imported)) {
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
// drop a reference
uint16_t count = 0;
if (UTILS_UNLIKELY(t->ref)) {
// the common case is that we don't have a ref handle
GLTextureRef* const ref = handle_cast<GLTextureRef*>(t->ref);
count = --(ref->count);
if (count == 0) {
destruct(t->ref, ref);
}
}
if (count == 0) {
// if this was the last reference, we destroy the refcount as well as
// the GL texture name itself.
gl.unbindTexture(t->gl.target, t->gl.id);
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
mPlatform.destroyExternalImageTexture(t->externalTexture);
} else {
glDeleteTextures(1, &t->gl.id);
}
} else {
// The Handle<HwTexture> is always destroyed. For extra precaution we also
// check that the GLTexture has a trivial destructor.
static_assert(std::is_trivially_destructible_v<GLTexture>);
}
} else {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
glDeleteRenderbuffers(1, &t->gl.id);
}
if (t->gl.sidecarRenderBufferMS) {
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
} else {
gl.unbindTexture(t->gl.target, t->gl.id);
}
destruct(th, t);
}
}
void OpenGLDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
DEBUG_MARKER()
if (rth) {
auto& gl = mContext;
GLRenderTarget* rt = handle_cast<GLRenderTarget*>(rth);
if (rt->gl.fbo) {
// first unbind this framebuffer if needed
gl.unbindFramebuffer(GL_FRAMEBUFFER);
}
if (rt->gl.fbo_read) {
// first unbind this framebuffer if needed
gl.unbindFramebuffer(GL_FRAMEBUFFER);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_UNLIKELY(gl.bugs.delay_fbo_destruction)) {
if (rt->gl.fbo) {
whenFrameComplete([fbo = rt->gl.fbo]() {
glDeleteFramebuffers(1, &fbo);
});
}
if (rt->gl.fbo_read) {
whenFrameComplete([fbo_read = rt->gl.fbo_read]() {
glDeleteFramebuffers(1, &fbo_read);
});
}
} else
#endif
{
if (rt->gl.fbo) {
glDeleteFramebuffers(1, &rt->gl.fbo);
}
if (rt->gl.fbo_read) {
glDeleteFramebuffers(1, &rt->gl.fbo_read);
}
}
destruct(rth, rt);
}
}
void OpenGLDriver::destroySwapChain(Handle<HwSwapChain> sch) {
DEBUG_MARKER()
if (sch) {
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
mPlatform.destroySwapChain(sc->swapChain);
destruct(sch, sc);
}
}
void OpenGLDriver::destroyStream(Handle<HwStream> sh) {
DEBUG_MARKER()
if (sh) {
GLStream* s = handle_cast<GLStream*>(sh);
// if this stream is still attached to a texture, detach it first
auto& texturesWithStreamsAttached = mTexturesWithStreamsAttached;
auto pos = std::find_if(
texturesWithStreamsAttached.begin(), texturesWithStreamsAttached.end(),
[s](GLTexture const* t) {
return t->hwStream == s;
});
if (pos != texturesWithStreamsAttached.end()) {
detachStream(*pos);
}
// and then destroy the stream. Only NATIVE streams have Platform::Stream associated.
if (s->streamType == StreamType::NATIVE) {
mPlatform.destroyStream(s->stream);
}
// finally destroy the HwStream handle
destruct(sh, s);
}
}
void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
DEBUG_MARKER()
if (tqh) {
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
mContext.destroyTimerQuery(tq);
destruct(tqh, tq);
}
}
void OpenGLDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
DEBUG_MARKER()
if (dslh) {
GLDescriptorSetLayout* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
destruct(dslh, dsl);
}
}
void OpenGLDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
DEBUG_MARKER()
if (dsh) {
// unbind the descriptor-set, to avoid use-after-free
for (auto& bound : mBoundDescriptorSets) {
if (bound.dsh == dsh) {
bound = {};
}
}
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
destruct(dsh, ds);
}
}
// ------------------------------------------------------------------------------------------------
// Synchronous APIs
// These are called on the application's thread
// ------------------------------------------------------------------------------------------------
Handle<HwStream> OpenGLDriver::createStreamNative(void* nativeStream) {
Platform::Stream* stream = mPlatform.createStream(nativeStream);
return initHandle<GLStream>(stream);
}
Handle<HwStream> OpenGLDriver::createStreamAcquired() {
return initHandle<GLStream>();
}
// Stashes an acquired external image and a release callback. The image is not bound to OpenGL until
// the subsequent call to beginFrame (see updateStreamAcquired).
//
// setAcquiredImage should be called by the user outside of beginFrame / endFrame, and should be
// called only once per frame. If the user pushes images to the same stream multiple times in a
// single frame, we emit a warning and honor only the final image, but still invoke all callbacks.
void OpenGLDriver::setAcquiredImage(Handle<HwStream> sh, void* hwbuffer,
CallbackHandler* handler, StreamCallback cb, void* userData) {
GLStream* glstream = handle_cast<GLStream*>(sh);
assert_invariant(glstream->streamType == StreamType::ACQUIRED);
if (UTILS_UNLIKELY(glstream->user_thread.pending.image)) {
scheduleRelease(glstream->user_thread.pending);
slog.w << "Acquired image is set more than once per frame." << io::endl;
}
glstream->user_thread.pending = mPlatform.transformAcquiredImage({
hwbuffer, cb, userData, handler });
if (glstream->user_thread.pending.image != nullptr) {
// If there's no pending image, do nothing. Note that GL_OES_EGL_image does not let you pass
// NULL to glEGLImageTargetTexture2DOES, and there is no concept of "detaching" an
// EGLimage from a texture.
mStreamsWithPendingAcquiredImage.push_back(glstream);
}
}
// updateStreams() and setAcquiredImage() are both called from on the application's thread
// and therefore do not require synchronization. The former is always called immediately before
// beginFrame, the latter is called by the user from anywhere outside beginFrame / endFrame.
void OpenGLDriver::updateStreams(DriverApi* driver) {
if (UTILS_UNLIKELY(!mStreamsWithPendingAcquiredImage.empty())) {
for (GLStream* s : mStreamsWithPendingAcquiredImage) {
assert_invariant(s);
assert_invariant(s->streamType == StreamType::ACQUIRED);
AcquiredImage const previousImage = s->user_thread.acquired;
s->user_thread.acquired = s->user_thread.pending;
s->user_thread.pending = { nullptr };
// Bind the stashed EGLImage to its corresponding GL texture as soon as we start
// making the GL calls for the upcoming frame.
driver->queueCommand([this, s, image = s->user_thread.acquired.image, previousImage]() {
auto& streams = mTexturesWithStreamsAttached;
auto pos = std::find_if(streams.begin(), streams.end(),
[s](GLTexture const* t) {
return t->hwStream == s;
});
if (pos != streams.end()) {
GLTexture* t = *pos;
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
if (mPlatform.setExternalImage(image, t->externalTexture)) {
// the target and id can be reset each time
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
}
}
if (previousImage.image) {
scheduleRelease(AcquiredImage(previousImage));
}
});
}
mStreamsWithPendingAcquiredImage.clear();
}
}
void OpenGLDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
if (sh) {
GLStream* s = handle_cast<GLStream*>(sh);
s->width = width;
s->height = height;
}
}
int64_t OpenGLDriver::getStreamTimestamp(Handle<HwStream> sh) {
if (sh) {
GLStream* s = handle_cast<GLStream*>(sh);
return s->user_thread.timestamp;
}
return 0;
}
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
GLFence* f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
mPlatform.destroyFence(f->fence);
}
destruct(fh, f);
}
}
FenceStatus OpenGLDriver::getFenceStatus(Handle<HwFence> fh) {
if (fh) {
GLFence* f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
if (f->fence == nullptr) {
// we can end-up here if:
// - the platform doesn't support h/w fences
if (UTILS_UNLIKELY(!mPlatform.canCreateFence())) {
return FenceStatus::ERROR;
}
// - wait() was called before the fence was asynchronously created.
return FenceStatus::TIMEOUT_EXPIRED;
}
return mPlatform.waitFence(f->fence, 0);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
else {
assert_invariant(f->state);
std::unique_lock lock(f->state->lock);
f->state->cond.wait_for(lock, std::chrono::nanoseconds(0), [&state = f->state]() {
return state->status != FenceStatus::TIMEOUT_EXPIRED;
});
return f->state->status;
}
#endif
}
return FenceStatus::ERROR;
}
bool OpenGLDriver::isTextureFormatSupported(TextureFormat format) {
const auto& ext = mContext.ext;
if (isETC2Compression(format)) {
return ext.EXT_texture_compression_etc2 ||
ext.WEBGL_compressed_texture_etc; // WEBGL specific, apparently contains ETC2
}
if (isS3TCSRGBCompression(format)) {
// see https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB.txt
return ext.WEBGL_compressed_texture_s3tc_srgb || // this is WEBGL specific
ext.EXT_texture_compression_s3tc_srgb || // this is ES specific
(ext.EXT_texture_compression_s3tc && ext.EXT_texture_sRGB);
}
if (isS3TCCompression(format)) {
return ext.EXT_texture_compression_s3tc || // this is ES specific
ext.WEBGL_compressed_texture_s3tc; // this is WEBGL specific
}
if (isRGTCCompression(format)) {
return ext.EXT_texture_compression_rgtc;
}
if (isBPTCCompression(format)) {
return ext.EXT_texture_compression_bptc;
}
if (isASTCCompression(format)) {
return ext.KHR_texture_compression_astc_hdr;
}
if (mContext.isES2()) {
return textureFormatToFormatAndType(format).first != GL_NONE;
}
return getInternalFormat(format) != 0;
}
bool OpenGLDriver::isTextureSwizzleSupported() {
#if defined(__EMSCRIPTEN__)
// WebGL2 doesn't support texture swizzle
// see https://registry.khronos.org/webgl/specs/latest/2.0/#5.19
return false;
#elif defined(BACKEND_OPENGL_VERSION_GLES)
return !mContext.isES2();
#else
return true;
#endif
}
bool OpenGLDriver::isTextureFormatMipmappable(TextureFormat format) {
// The OpenGL spec for GenerateMipmap stipulates that it returns INVALID_OPERATION unless
// the sized internal format is both color-renderable and texture-filterable.
switch (format) {
case TextureFormat::DEPTH16:
case TextureFormat::DEPTH24:
case TextureFormat::DEPTH32F:
case TextureFormat::DEPTH24_STENCIL8:
case TextureFormat::DEPTH32F_STENCIL8:
return false;
default:
return isRenderTargetFormatSupported(format);
}
}
bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) {
// Supported formats per http://docs.gl/es3/glRenderbufferStorage, note that desktop OpenGL may
// support more formats, but it requires querying GL_INTERNALFORMAT_SUPPORTED which is not
// available in OpenGL ES.
auto& gl = mContext;
if (UTILS_UNLIKELY(gl.isES2())) {
auto [es2format, type] = textureFormatToFormatAndType(format);
return es2format != GL_NONE && type != GL_NONE;
}
switch (format) {
// Core formats.
case TextureFormat::R8:
case TextureFormat::R8UI:
case TextureFormat::R8I:
case TextureFormat::STENCIL8:
case TextureFormat::R16UI:
case TextureFormat::R16I:
case TextureFormat::RG8:
case TextureFormat::RG8UI:
case TextureFormat::RG8I:
case TextureFormat::RGB565:
case TextureFormat::RGB5_A1:
case TextureFormat::RGBA4:
case TextureFormat::DEPTH16:
case TextureFormat::RGB8:
case TextureFormat::DEPTH24:
case TextureFormat::R32UI:
case TextureFormat::R32I:
case TextureFormat::RG16UI:
case TextureFormat::RG16I:
case TextureFormat::RGBA8:
case TextureFormat::SRGB8_A8:
case TextureFormat::RGB10_A2:
case TextureFormat::RGBA8UI:
case TextureFormat::RGBA8I:
case TextureFormat::DEPTH32F:
case TextureFormat::DEPTH24_STENCIL8:
case TextureFormat::DEPTH32F_STENCIL8:
case TextureFormat::RG32UI:
case TextureFormat::RG32I:
case TextureFormat::RGBA16UI:
case TextureFormat::RGBA16I:
return true;
// Three-component SRGB is a color-renderable texture format in core OpenGL on desktop.
case TextureFormat::SRGB8:
return mContext.isAtLeastGL<4, 5>();
// Half-float formats, requires extension.
case TextureFormat::R16F:
case TextureFormat::RG16F:
case TextureFormat::RGBA16F:
return gl.ext.EXT_color_buffer_float || gl.ext.EXT_color_buffer_half_float;
// RGB16F is only supported with EXT_color_buffer_half_float, however
// some WebGL implementations do not consider this extension to be sufficient:
// https://bugs.chromium.org/p/chromium/issues/detail?id=941671#c10
case TextureFormat::RGB16F:
#if defined(__EMSCRIPTEN__)
return false;
#else
return gl.ext.EXT_color_buffer_half_float;
#endif
// Float formats from GL_EXT_color_buffer_float
case TextureFormat::R32F:
case TextureFormat::RG32F:
case TextureFormat::RGBA32F:
return gl.ext.EXT_color_buffer_float;
// RGB_11_11_10 is only supported with some specific extensions
case TextureFormat::R11F_G11F_B10F:
return gl.ext.EXT_color_buffer_float || gl.ext.APPLE_color_buffer_packed_float;
default:
return false;
}
}
bool OpenGLDriver::isFrameBufferFetchSupported() {
auto& gl = mContext;
return gl.ext.EXT_shader_framebuffer_fetch;
}
bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() {
return isFrameBufferFetchSupported();
}
bool OpenGLDriver::isFrameTimeSupported() {
return TimerQueryFactory::isGpuTimeSupported();
}
bool OpenGLDriver::isAutoDepthResolveSupported() {
// TODO: this should return true only for GLES3.1+ and EXT_multisampled_render_to_texture2
return true;
}
bool OpenGLDriver::isSRGBSwapChainSupported() {
if (UTILS_UNLIKELY(mContext.isES2())) {
// On ES2 backend (i.e. feature level 0), we always pretend to the client that sRGB
// SwapChain are available. If we actually have that feature, it'll be used, otherwise
// we emulate it in the shaders.
return true;
}
return mPlatform.isSRGBSwapChainSupported();
}
bool OpenGLDriver::isProtectedContentSupported() {
return mPlatform.isProtectedContextSupported();
}
bool OpenGLDriver::isStereoSupported() {
// Instanced-stereo requires instancing and EXT_clip_cull_distance.
// Multiview-stereo requires ES 3.0 and OVR_multiview2.
if (UTILS_UNLIKELY(mContext.isES2())) {
return false;
}
switch (mDriverConfig.stereoscopicType) {
case backend::StereoscopicType::INSTANCED:
return mContext.ext.EXT_clip_cull_distance;
case backend::StereoscopicType::MULTIVIEW:
return mContext.ext.OVR_multiview2;
case backend::StereoscopicType::NONE:
return false;
}
}
bool OpenGLDriver::isParallelShaderCompileSupported() {
return mShaderCompilerService.isParallelShaderCompileSupported();
}
bool OpenGLDriver::isDepthStencilResolveSupported() {
return true;
}
bool OpenGLDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool OpenGLDriver::isProtectedTexturesSupported() {
return getContext().ext.EXT_protected_textures;
}
bool OpenGLDriver::isDepthClampSupported() {
return getContext().ext.EXT_depth_clamp;
}
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
return mContext.bugs.split_easu;
case Workaround::ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP:
return mContext.bugs.allow_read_only_ancillary_feedback_loop;
case Workaround::ADRENO_UNIFORM_ARRAY_CRASH:
return mContext.bugs.enable_initialize_non_used_uniform_array;
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
return mContext.bugs.disable_blit_into_texture_array;
case Workaround::POWER_VR_SHADER_WORKAROUNDS:
return mContext.bugs.powervr_shader_workarounds;
default:
return false;
}
return false;
}
FeatureLevel OpenGLDriver::getFeatureLevel() {
return mContext.getFeatureLevel();
}
math::float2 OpenGLDriver::getClipSpaceParams() {
return mContext.ext.EXT_clip_control ?
// z-coordinate of virtual and physical clip-space is in [-w, 0]
math::float2{ 1.0f, 0.0f } :
// z-coordinate of virtual clip-space is in [-w,0], physical is in [-w, w]
math::float2{ 2.0f, -1.0f };
}
uint8_t OpenGLDriver::getMaxDrawBuffers() {
return std::min(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, uint8_t(mContext.gets.max_draw_buffers));
}
size_t OpenGLDriver::getMaxUniformBufferSize() {
return mContext.gets.max_uniform_block_size;
}
// ------------------------------------------------------------------------------------------------
// Swap chains
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::commit(Handle<HwSwapChain> sch) {
DEBUG_MARKER()
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
mPlatform.commit(sc->swapChain);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_UNLIKELY(!mFrameCompleteOps.empty())) {
whenGpuCommandsComplete([ops = std::move(mFrameCompleteOps)]() {
for (auto&& op: ops) {
op();
}
});
}
#endif
}
void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
DEBUG_MARKER()
GLSwapChain* scDraw = handle_cast<GLSwapChain*>(schDraw);
GLSwapChain* scRead = handle_cast<GLSwapChain*>(schRead);
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain,
[this]() {
// OpenGL context is about to change, unbind everything
mContext.unbindEverything();
},
[this](size_t index) {
// OpenGL context has changed, resynchronize the state with the cache
mContext.synchronizeStateAndCache(index);
slog.d << "*** OpenGL context change : " << (index ? "protected" : "default") << io::endl;
});
mCurrentDrawSwapChain = scDraw;
// From the GL spec for glViewport and glScissor:
// When a GL context is first attached to a window, width and height are set to the
// dimensions of that window.
// So basically, our viewport/scissor can be reset to "something" here.
mContext.state.window.viewport = {};
mContext.state.window.scissor = {};
}
// ------------------------------------------------------------------------------------------------
// Updating driver objects
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mHandleAllocator.associateTagToHandle(handleId, std::move(tag));
}
void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
uint32_t index, Handle<HwBufferObject> boh) {
DEBUG_MARKER()
GLVertexBuffer* vb = handle_cast<GLVertexBuffer *>(vbh);
GLBufferObject* bo = handle_cast<GLBufferObject *>(boh);
assert_invariant(bo->gl.binding == GL_ARRAY_BUFFER);
// If the specified VBO handle is different from what's already in the slot, then update the
// slot and bump the cyclical version number. Dependent VAOs use the version number to detect
// when they should be updated.
if (vb->gl.buffers[index] != bo->gl.id) {
vb->gl.buffers[index] = bo->gl.id;
static constexpr uint32_t kMaxVersion =
std::numeric_limits<decltype(vb->bufferObjectsVersion)>::max();
const uint32_t version = vb->bufferObjectsVersion;
vb->bufferObjectsVersion = (version + 1) % kMaxVersion;
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::updateIndexBuffer(
Handle<HwIndexBuffer> ibh, BufferDescriptor&& p, uint32_t byteOffset) {
DEBUG_MARKER()
auto& gl = mContext;
GLIndexBuffer* ib = handle_cast<GLIndexBuffer *>(ibh);
assert_invariant(ib->elementSize == 2 || ib->elementSize == 4);
gl.bindVertexArray(nullptr);
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, byteOffset, (GLsizeiptr)p.size, p.buffer);
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::updateBufferObject(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t byteOffset) {
DEBUG_MARKER()
auto& gl = mContext;
GLBufferObject* bo = handle_cast<GLBufferObject *>(boh);
assert_invariant(bd.size + byteOffset <= bo->byteCount);
if (bo->gl.binding == GL_ARRAY_BUFFER) {
gl.bindVertexArray(nullptr);
}
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
assert_invariant(bo->gl.buffer);
memcpy(static_cast<uint8_t*>(bo->gl.buffer) + byteOffset, bd.buffer, bd.size);
bo->age++;
} else {
assert_invariant(bo->gl.id);
gl.bindBuffer(bo->gl.binding, bo->gl.id);
if (byteOffset == 0 && bd.size == bo->byteCount) {
// it looks like it's generally faster (or not worse) to use glBufferData()
glBufferData(bo->gl.binding, (GLsizeiptr)bd.size, bd.buffer, getBufferUsage(bo->usage));
} else {
// glBufferSubData() could be catastrophically inefficient if several are
// issued during the same frame. Currently, we're not doing that though.
glBufferSubData(bo->gl.binding, byteOffset, (GLsizeiptr)bd.size, bd.buffer);
}
}
scheduleDestroy(std::move(bd));
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::updateBufferObjectUnsynchronized(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t byteOffset) {
DEBUG_MARKER()
if (UTILS_UNLIKELY(mContext.isES2())) {
updateBufferObject(boh, std::move(bd), byteOffset);
return;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if constexpr (!HAS_MAPBUFFERS) {
updateBufferObject(boh, std::move(bd), byteOffset);
} else {
GLBufferObject* bo = handle_cast<GLBufferObject*>(boh);
assert_invariant(bo->gl.id);
assert_invariant(bd.size + byteOffset <= bo->byteCount);
if (bo->gl.binding != GL_UNIFORM_BUFFER) {
// TODO: use updateBuffer() for all types of buffer? Make sure GL supports that.
updateBufferObject(boh, std::move(bd), byteOffset);
} else {
auto& gl = mContext;
gl.bindBuffer(bo->gl.binding, bo->gl.id);
retry:
void* const vaddr = glMapBufferRange(bo->gl.binding, byteOffset, (GLsizeiptr)bd.size,
GL_MAP_WRITE_BIT |
GL_MAP_INVALIDATE_RANGE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT);
if (UTILS_LIKELY(vaddr)) {
memcpy(vaddr, bd.buffer, bd.size);
if (UTILS_UNLIKELY(glUnmapBuffer(bo->gl.binding) == GL_FALSE)) {
// According to the spec, UnmapBuffer can return FALSE in rare conditions (e.g.
// during a screen mode change). Note that this is not a GL error, and we can handle
// it by simply making a second attempt.
goto retry; // NOLINT(cppcoreguidelines-avoid-goto,hicpp-avoid-goto)
}
} else {
// handle mapping error, revert to glBufferSubData()
glBufferSubData(bo->gl.binding, byteOffset, (GLsizeiptr)bd.size, bd.buffer);
}
scheduleDestroy(std::move(bd));
}
}
CHECK_GL_ERROR(utils::slog.e)
#endif
}
void OpenGLDriver::resetBufferObject(Handle<HwBufferObject> boh) {
DEBUG_MARKER()
auto& gl = mContext;
GLBufferObject* bo = handle_cast<GLBufferObject*>(boh);
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
// nothing to do here
} else {
assert_invariant(bo->gl.id);
gl.bindBuffer(bo->gl.binding, bo->gl.id);
glBufferData(bo->gl.binding, bo->byteCount, nullptr, getBufferUsage(bo->usage));
}
}
void OpenGLDriver::update3DImage(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data) {
DEBUG_MARKER()
GLTexture* t = handle_cast<GLTexture *>(th);
if (data.type == PixelDataType::COMPRESSED) {
setCompressedTextureData(t,
level, xoffset, yoffset, zoffset, width, height, depth, std::move(data));
} else {
setTextureData(t,
level, xoffset, yoffset, zoffset, width, height, depth, std::move(data));
}
}
void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
DEBUG_MARKER()
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture *>(th);
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
assert_invariant(t->gl.target != GL_TEXTURE_2D_MULTISAMPLE);
#endif
// Note: glGenerateMimap can also fail if the internal format is not both
// color-renderable and filterable (i.e.: doesn't work for depth)
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
glGenerateMipmap(t->gl.target);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& p) {
auto& gl = mContext;
assert_invariant(t != nullptr);
assert_invariant(xoffset + width <= std::max(1u, t->width >> level));
assert_invariant(yoffset + height <= std::max(1u, t->height >> level));
assert_invariant(t->samples <= 1);
if (UTILS_UNLIKELY(t->gl.target == GL_TEXTURE_EXTERNAL_OES)) {
// this is in fact an external texture, this becomes a no-op.
return;
}
GLenum glFormat;
GLenum glType;
if (mContext.isES2()) {
auto formatAndType = textureFormatToFormatAndType(t->format);
glFormat = formatAndType.first;
glType = formatAndType.second;
} else {
glFormat = getFormat(p.format);
glType = getType(p.type);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!gl.isES2()) {
gl.pixelStore(GL_UNPACK_ROW_LENGTH, GLint(p.stride));
}
#endif
gl.pixelStore(GL_UNPACK_ALIGNMENT, GLint(p.alignment));
// This is equivalent to using GL_UNPACK_SKIP_PIXELS and GL_UNPACK_SKIP_ROWS
using PBD = PixelBufferDescriptor;
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
void const* const buffer = static_cast<char const*>(p.buffer)
+ bpp* p.left + bpr * p.top + bpl * 0; // TODO: PBD should have a p.depth
switch (t->target) {
case SamplerType::SAMPLER_EXTERNAL:
// if we get there, it's because the user is trying to use an external texture,
// but it's not supported, so instead, we behave like a texture2d.
// fallthrough...
case SamplerType::SAMPLER_2D:
// NOTE: GL_TEXTURE_2D_MULTISAMPLE is not allowed
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(t->gl.target == GL_TEXTURE_2D);
glTexSubImage2D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_3D:
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
assert_invariant(zoffset + depth <= std::max(1u, t->depth >> level));
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(t->gl.target == GL_TEXTURE_3D);
glTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, buffer);
#endif
break;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
assert_invariant(zoffset + depth <= t->depth);
// NOTE: GL_TEXTURE_2D_MULTISAMPLE is not allowed
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(t->gl.target == GL_TEXTURE_2D_ARRAY ||
t->gl.target == GL_TEXTURE_CUBE_MAP_ARRAY);
glTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, buffer);
#endif
break;
case SamplerType::SAMPLER_CUBEMAP: {
assert_invariant(t->gl.target == GL_TEXTURE_CUBE_MAP);
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(width == height);
const size_t faceSize = PixelBufferDescriptor::computeDataSize(
p.format, p.type, p.stride ? p.stride : width, height, p.alignment);
assert_invariant(zoffset + depth <= 6);
UTILS_NOUNROLL
for (size_t face = 0; face < depth; face++) {
GLenum const target = getCubemapTarget(zoffset + face);
glTexSubImage2D(target, GLint(level), GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), glFormat, glType,
static_cast<uint8_t const*>(buffer) + faceSize * face);
}
break;
}
}
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::setCompressedTextureData(GLTexture* t, uint32_t level,
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& p) {
auto& gl = mContext;
assert_invariant(xoffset + width <= std::max(1u, t->width >> level));
assert_invariant(yoffset + height <= std::max(1u, t->height >> level));
assert_invariant(zoffset + depth <= t->depth);
assert_invariant(t->samples <= 1);
if (UTILS_UNLIKELY(t->gl.target == GL_TEXTURE_EXTERNAL_OES)) {
// this is in fact an external texture, this becomes a no-op.
return;
}
// TODO: maybe assert that the CompressedPixelDataType is the same as the internalFormat
GLsizei const imageSize = GLsizei(p.imageSize);
// TODO: maybe assert the size is right (b/c we can compute it ourselves)
switch (t->target) {
case SamplerType::SAMPLER_EXTERNAL:
// if we get there, it's because the user is trying to use an external texture,
// but it's not supported, so instead, we behave like a texture2d.
// fallthrough...
case SamplerType::SAMPLER_2D:
// NOTE: GL_TEXTURE_2D_MULTISAMPLE is not allowed
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(t->gl.target == GL_TEXTURE_2D);
glCompressedTexSubImage2D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height),
t->gl.internalFormat, imageSize, p.buffer);
break;
case SamplerType::SAMPLER_3D:
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(t->gl.target == GL_TEXTURE_3D);
glCompressedTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth),
t->gl.internalFormat, imageSize, p.buffer);
#endif
break;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
assert_invariant(t->gl.target == GL_TEXTURE_2D_ARRAY ||
t->gl.target == GL_TEXTURE_CUBE_MAP_ARRAY);
glCompressedTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth),
t->gl.internalFormat, imageSize, p.buffer);
#endif
break;
case SamplerType::SAMPLER_CUBEMAP: {
assert_invariant(t->gl.target == GL_TEXTURE_CUBE_MAP);
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
assert_invariant(width == height);
const size_t faceSize = PixelBufferDescriptor::computeDataSize(
p.format, p.type, p.stride ? p.stride : width, height, p.alignment);
UTILS_NOUNROLL
for (size_t face = 0; face < depth; face++) {
GLenum const target = getCubemapTarget(zoffset + face);
glCompressedTexSubImage2D(target, GLint(level), GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), t->gl.internalFormat,
imageSize, static_cast<uint8_t const*>(p.buffer) + faceSize * face);
}
break;
}
}
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
mPlatform.retainExternalImage(image);
}
void OpenGLDriver::setupExternalImage(void* image) {
mPlatform.retainExternalImage(image);
}
void OpenGLDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
auto& gl = mContext;
if (gl.ext.OES_EGL_image_external_essl3) {
DEBUG_MARKER()
GLTexture* t = handle_cast<GLTexture*>(th);
if (UTILS_LIKELY(sh)) {
GLStream* s = handle_cast<GLStream*>(sh);
if (UTILS_LIKELY(!t->hwStream)) {
// we're not attached already
attachStream(t, s);
} else {
if (s->stream != t->hwStream->stream) {
// attaching to a different stream, detach the old one first
replaceStream(t, s);
}
}
} else if (t->hwStream) {
// do nothing if we're not attached already
detachStream(t);
}
}
}
UTILS_NOINLINE
void OpenGLDriver::attachStream(GLTexture* t, GLStream* hwStream) noexcept {
mTexturesWithStreamsAttached.push_back(t);
switch (hwStream->streamType) {
case StreamType::NATIVE:
mPlatform.attach(hwStream->stream, t->gl.id);
break;
case StreamType::ACQUIRED:
break;
}
t->hwStream = hwStream;
}
UTILS_NOINLINE
void OpenGLDriver::detachStream(GLTexture* t) noexcept {
auto& gl = mContext;
auto& texturesWithStreamsAttached = mTexturesWithStreamsAttached;
auto pos = std::find(texturesWithStreamsAttached.begin(), texturesWithStreamsAttached.end(), t);
if (pos != texturesWithStreamsAttached.end()) {
texturesWithStreamsAttached.erase(pos);
}
GLStream* s = static_cast<GLStream*>(t->hwStream); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
switch (s->streamType) {
case StreamType::NATIVE:
mPlatform.detach(t->hwStream->stream);
// ^ this deletes the texture id
break;
case StreamType::ACQUIRED:
gl.unbindTexture(t->gl.target, t->gl.id);
glDeleteTextures(1, &t->gl.id);
break;
}
glGenTextures(1, &t->gl.id);
t->hwStream = nullptr;
}
UTILS_NOINLINE
void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexcept {
assert_invariant(newStream && "Do not use replaceStream to detach a stream.");
// This could be implemented via detachStream + attachStream but inlining allows
// a few small optimizations, like not touching the mExternalStreams list.
GLStream* oldStream = static_cast<GLStream*>(texture->hwStream); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
switch (oldStream->streamType) {
case StreamType::NATIVE:
mPlatform.detach(texture->hwStream->stream);
// ^ this deletes the texture id
break;
case StreamType::ACQUIRED:
break;
}
switch (newStream->streamType) {
case StreamType::NATIVE:
glGenTextures(1, &texture->gl.id);
mPlatform.attach(newStream->stream, texture->gl.id);
break;
case StreamType::ACQUIRED:
// Just re-use the old texture id.
break;
}
texture->hwStream = newStream;
}
void OpenGLDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
DEBUG_MARKER()
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
mContext.beginTimeElapsedQuery(tq);
}
void OpenGLDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
DEBUG_MARKER()
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
mContext.endTimeElapsedQuery(*this, tq);
}
TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
return TimerQueryFactoryInterface::getTimerQueryValue(tq, elapsedTime);
}
void OpenGLDriver::compilePrograms(CompilerPriorityQueue,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user);
}
}
void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
const RenderPassParams& params) {
DEBUG_MARKER()
getShaderCompilerService().tick();
auto& gl = mContext;
mRenderPassTarget = rth;
mRenderPassParams = params;
GLRenderTarget* rt = handle_cast<GLRenderTarget*>(rth);
// If we're rendering into the default render target (i.e. into the current SwapChain),
// get the value of the output colorspace from there, otherwise it's always linear.
assert_invariant(!rt->gl.isDefault || mCurrentDrawSwapChain);
mRec709OutputColorspace = rt->gl.isDefault ? mCurrentDrawSwapChain->rec709 : false;
const TargetBufferFlags clearFlags = params.flags.clear & rt->targets;
TargetBufferFlags discardFlags = params.flags.discardStart & rt->targets;
GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
// each render-pass starts with a disabled scissor
gl.disable(GL_SCISSOR_TEST);
if (gl.ext.EXT_discard_framebuffer
&& !gl.bugs.disable_invalidate_framebuffer) {
AttachmentArray attachments; // NOLINT
GLsizei const attachmentCount = getAttachments(attachments, discardFlags, !fbo);
if (attachmentCount) {
gl.procs.invalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
CHECK_GL_ERROR(utils::slog.e)
} else {
// It's important to clear the framebuffer before drawing, as it resets
// the fb to a known state (resets fb compression and possibly other things).
// So we use glClear instead of glInvalidateFramebuffer
clearWithRasterPipe(discardFlags & ~clearFlags, { 0.0f }, 0.0f, 0);
}
if (rt->gl.fbo_read) {
// we have a multi-sample RenderTarget with non multi-sample attachments (i.e. this is the
// EXT_multisampled_render_to_texture emulation).
// We would need to perform a "backward" resolve, i.e. load the resolved texture into the
// tile, everything must appear as though the multi-sample buffer was lost.
// However, Filament specifies that a non multi-sample attachment to a
// multi-sample RenderTarget is always discarded. We do this because implementing
// the load on Metal is not trivial, and it's not a feature we rely on at this time.
discardFlags |= rt->gl.resolve;
}
if (any(clearFlags)) {
clearWithRasterPipe(clearFlags,
params.clearColor, (GLfloat)params.clearDepth, (GLint)params.clearStencil);
}
// we need to reset those after we call clearWithRasterPipe()
mRenderPassColorWrite = any(clearFlags & TargetBufferFlags::COLOR_ALL);
mRenderPassDepthWrite = any(clearFlags & TargetBufferFlags::DEPTH);
mRenderPassStencilWrite = any(clearFlags & TargetBufferFlags::STENCIL);
static_assert(sizeof(GLsizei) >= sizeof(uint32_t));
gl.viewport(params.viewport.left, params.viewport.bottom,
(GLsizei)std::min(uint32_t(std::numeric_limits<int32_t>::max()), params.viewport.width),
(GLsizei)std::min(uint32_t(std::numeric_limits<int32_t>::max()), params.viewport.height));
gl.depthRange(params.depthRange.near, params.depthRange.far);
#ifndef NDEBUG
// clear the discarded (but not the cleared ones) buffers in debug builds
clearWithRasterPipe(discardFlags & ~clearFlags,
{ 1, 0, 0, 1 }, 1.0, 0);
#endif
}
void OpenGLDriver::endRenderPass(int) {
DEBUG_MARKER()
auto& gl = mContext;
assert_invariant(mRenderPassTarget); // endRenderPass() called without beginRenderPass()?
GLRenderTarget const* const rt = handle_cast<GLRenderTarget*>(mRenderPassTarget);
TargetBufferFlags discardFlags = mRenderPassParams.flags.discardEnd & rt->targets;
if (rt->gl.fbo_read) {
resolvePass(ResolveAction::STORE, rt, discardFlags);
}
if (!mRenderPassColorWrite) {
// ignore discard flags if the buffer wasn't written at all
discardFlags &= ~TargetBufferFlags::COLOR_ALL;
}
if (!mRenderPassDepthWrite) {
// ignore discard flags if the buffer wasn't written at all
discardFlags &= ~TargetBufferFlags::DEPTH;
}
if (!mRenderPassStencilWrite) {
// ignore discard flags if the buffer wasn't written at all
discardFlags &= ~TargetBufferFlags::STENCIL;
}
if (rt->gl.isDefault) {
assert_invariant(mCurrentDrawSwapChain);
discardFlags &= ~mPlatform.getPreservedFlags(mCurrentDrawSwapChain->swapChain);
}
if (gl.ext.EXT_discard_framebuffer) {
auto effectiveDiscardFlags = discardFlags;
if (gl.bugs.invalidate_end_only_if_invalidate_start) {
effectiveDiscardFlags &= mRenderPassParams.flags.discardStart;
}
if (!gl.bugs.disable_invalidate_framebuffer) {
// we wouldn't have to bind the framebuffer if we had glInvalidateNamedFramebuffer()
GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
AttachmentArray attachments; // NOLINT
GLsizei const attachmentCount = getAttachments(attachments, effectiveDiscardFlags, !fbo);
if (attachmentCount) {
gl.procs.invalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
CHECK_GL_ERROR(utils::slog.e)
}
}
#ifndef NDEBUG
// clear the discarded buffers in debug builds
mContext.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
mContext.disable(GL_SCISSOR_TEST);
clearWithRasterPipe(discardFlags,
{ 0, 1, 0, 1 }, 1.0, 0);
#endif
mRenderPassTarget.clear();
}
void OpenGLDriver::nextSubpass(int) {}
void OpenGLDriver::resolvePass(ResolveAction action, GLRenderTarget const* rt,
TargetBufferFlags discardFlags) noexcept {
if (UTILS_UNLIKELY(getContext().isES2())) {
// ES2 doesn't have manual resolve capabilities
return;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
assert_invariant(rt->gl.fbo_read);
auto& gl = mContext;
const TargetBufferFlags resolve = rt->gl.resolve & ~discardFlags;
GLbitfield const mask = getAttachmentBitfield(resolve);
if (UTILS_UNLIKELY(mask)) {
// we can only resolve COLOR0 at the moment
assert_invariant(!(rt->targets &
(TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
GLint read = (GLint)rt->gl.fbo_read;
GLint draw = (GLint)rt->gl.fbo;
if (action == ResolveAction::STORE) {
std::swap(read, draw);
}
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, read);
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, draw);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(0, 0, (GLint)rt->width, (GLint)rt->height,
0, 0, (GLint)rt->width, (GLint)rt->height, mask, GL_NEAREST);
CHECK_GL_ERROR(utils::slog.e)
}
#endif
}
GLsizei OpenGLDriver::getAttachments(AttachmentArray& attachments,
TargetBufferFlags buffers, bool isDefaultFramebuffer) noexcept {
GLsizei attachmentCount = 0;
// the default framebuffer uses different constants!!!
if (any(buffers & TargetBufferFlags::COLOR0)) {
attachments[attachmentCount++] = isDefaultFramebuffer ? GL_COLOR : GL_COLOR_ATTACHMENT0;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (any(buffers & TargetBufferFlags::COLOR1)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT1;
}
if (any(buffers & TargetBufferFlags::COLOR2)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT2;
}
if (any(buffers & TargetBufferFlags::COLOR3)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT3;
}
if (any(buffers & TargetBufferFlags::COLOR4)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT4;
}
if (any(buffers & TargetBufferFlags::COLOR5)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT5;
}
if (any(buffers & TargetBufferFlags::COLOR6)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT6;
}
if (any(buffers & TargetBufferFlags::COLOR7)) {
assert_invariant(!isDefaultFramebuffer);
attachments[attachmentCount++] = GL_COLOR_ATTACHMENT7;
}
#endif
if (any(buffers & TargetBufferFlags::DEPTH)) {
attachments[attachmentCount++] = isDefaultFramebuffer ? GL_DEPTH : GL_DEPTH_ATTACHMENT;
}
if (any(buffers & TargetBufferFlags::STENCIL)) {
attachments[attachmentCount++] = isDefaultFramebuffer ? GL_STENCIL : GL_STENCIL_ATTACHMENT;
}
return attachmentCount;
}
// Sets up a scissor rectangle that automatically gets clipped against the viewport.
void OpenGLDriver::setScissor(Viewport const& scissor) noexcept {
constexpr uint32_t maxvalu = std::numeric_limits<int32_t>::max();
auto& gl = mContext;
// TODO: disable scissor when it is bigger than the current surface?
if (scissor.left == 0 && scissor.bottom == 0 &&
scissor.width >= maxvalu && scissor.height >= maxvalu) {
gl.disable(GL_SCISSOR_TEST);
return;
}
gl.setScissor(
GLint(scissor.left), GLint(scissor.bottom),
GLint(scissor.width), GLint(scissor.height));
gl.enable(GL_SCISSOR_TEST);
}
// ------------------------------------------------------------------------------------------------
// Setting rendering state
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::insertEventMarker(char const* string) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
auto& gl = mContext;
if (gl.ext.EXT_debug_marker) {
glInsertEventMarkerEXT(GLsizei(strlen(string)), string);
}
#endif
#endif
}
void OpenGLDriver::pushGroupMarker(char const* string) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
glPushGroupMarkerEXT(GLsizei(strlen(string)), string);
}
#endif
#endif
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
#endif
#endif
}
void OpenGLDriver::popGroupMarker(int) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
glPopGroupMarkerEXT();
}
#endif
#endif
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
#endif
#endif
}
void OpenGLDriver::startCapture(int) {
}
void OpenGLDriver::stopCapture(int) {
}
// ------------------------------------------------------------------------------------------------
// Read-back ops
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
uint32_t x, uint32_t y, uint32_t width, uint32_t height,
PixelBufferDescriptor&& p) {
DEBUG_MARKER()
auto& gl = mContext;
GLenum const glFormat = getFormat(p.format);
GLenum const glType = getType(p.type);
gl.pixelStore(GL_PACK_ALIGNMENT, (GLint)p.alignment);
/*
* glReadPixel() operation...
*
* Framebuffer as seen on User buffer
* screen
* +--------------------+
* | | stride alignment
* | | ----------------------->-->
* | | +----------------------+--+ low addresses
* | | | | | |
* | w | | | bottom | |
* | <---------> | | V | |
* | +---------+ | | +.........+ | |
* | | ^ | | =====> | | | | |
* | x | h| | | |left | | | |
* +------>| v | | +---->| | | |
* | +.........+ | | +---------+ | |
* | ^ | | | |
* | y | | +----------------------+--+ high addresses
* +------------+-------+
* Image is "flipped" vertically
* "bottom" is from the "top" (low addresses)
* of the buffer.
*/
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
using PBD = PixelBufferDescriptor;
// The PBO only needs to accommodate the area we're reading, with alignment.
auto const pboSize = (GLsizeiptr)PBD::computeDataSize(
p.format, p.type, width, height, p.alignment);
if (UTILS_UNLIKELY(gl.isES2())) {
void* buffer = malloc(pboSize);
if (buffer) {
gl.bindFramebuffer(GL_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, buffer);
CHECK_GL_ERROR(utils::slog.e)
// now we need to flip the buffer vertically to match our API
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const dstBpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
char* pDst = (char*)p.buffer + p.left * bpp + dstBpr * (p.top + height - 1);
size_t const srcBpr = PBD::computeDataSize(p.format, p.type, width, 1, p.alignment);
char const* pSrc = (char const*)buffer;
for (size_t i = 0; i < height; ++i) {
memcpy(pDst, pSrc, bpp * width);
pSrc += srcBpr;
pDst -= dstBpr;
}
}
free(buffer);
scheduleDestroy(std::move(p));
return;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
// glReadPixel doesn't resolve automatically, but it does with the auto-resolve extension,
// which we're always emulating. So if we have a resolved fbo (fbo_read), use that instead.
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
GLuint pbo;
glGenBuffers(1, &pbo);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, pboSize, nullptr, GL_STATIC_DRAW);
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, nullptr);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
// we're forced to make a copy on the heap because otherwise it deletes std::function<> copy
// constructor.
auto* const pUserBuffer = new PixelBufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, width, height, pbo, pboSize, pUserBuffer]() mutable {
PixelBufferDescriptor& p = *pUserBuffer;
auto& gl = mContext;
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
void* vaddr = nullptr;
#if defined(__EMSCRIPTEN__)
std::unique_ptr<uint8_t[]> clientBuffer = std::make_unique<uint8_t[]>(pboSize);
glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, pboSize, clientBuffer.get());
vaddr = clientBuffer.get();
#else
vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, pboSize, GL_MAP_READ_BIT);
#endif
if (vaddr) {
// now we need to flip the buffer vertically to match our API
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const dstBpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
char* pDst = (char*)p.buffer + p.left * bpp + dstBpr * (p.top + height - 1);
size_t const srcBpr = PBD::computeDataSize(p.format, p.type, width, 1, p.alignment);
char const* pSrc = (char const*)vaddr;
for (size_t i = 0; i < height; ++i) {
memcpy(pDst, pSrc, bpp * width);
pSrc += srcBpr;
pDst -= dstBpr;
}
#if !defined(__EMSCRIPTEN__)
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
#endif
}
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteBuffers(1, &pbo);
scheduleDestroy(std::move(p));
delete pUserBuffer;
CHECK_GL_ERROR(utils::slog.e)
});
#endif
}
void OpenGLDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLBufferObject const* bo = handle_cast<GLBufferObject const*>(boh);
// TODO: measure the two solutions
if constexpr (true) {
// schedule a copy of the buffer we're reading into a PBO, this *should* happen
// asynchronously without stalling the CPU.
GLuint pbo;
glGenBuffers(1, &pbo);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, (GLsizeiptr)size, nullptr, GL_STATIC_DRAW);
gl.bindBuffer(bo->gl.binding, bo->gl.id);
glCopyBufferSubData(bo->gl.binding, GL_PIXEL_PACK_BUFFER, offset, 0, size);
gl.bindBuffer(bo->gl.binding, 0);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
// then, we schedule a mapBuffer of the PBO later, once the fence has signaled
auto* pUserBuffer = new BufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, size, pbo, pUserBuffer]() mutable {
BufferDescriptor& p = *pUserBuffer;
auto& gl = mContext;
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
void* vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT);
if (vaddr) {
memcpy(p.buffer, vaddr, size);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteBuffers(1, &pbo);
scheduleDestroy(std::move(p));
delete pUserBuffer;
CHECK_GL_ERROR(utils::slog.e)
});
} else {
gl.bindBuffer(bo->gl.binding, bo->gl.id);
// TODO: this glMapBufferRange may stall. Ideally we want to use whenGpuCommandsComplete
// but that's tricky because boh could be destroyed right after this call.
void* vaddr = glMapBufferRange(bo->gl.binding, offset, size, GL_MAP_READ_BIT);
if (vaddr) {
memcpy(p.buffer, vaddr, size);
glUnmapBuffer(bo->gl.binding);
}
gl.bindBuffer(bo->gl.binding, 0);
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
}
#endif
}
void OpenGLDriver::runEveryNowAndThen(std::function<bool()> fn) noexcept {
mEveryNowAndThenOps.push_back(std::move(fn));
}
void OpenGLDriver::executeEveryNowAndThenOps() noexcept {
auto& v = mEveryNowAndThenOps;
auto it = v.begin();
while (it != v.end()) {
if ((*it)()) {
it = v.erase(it);
} else {
++it;
}
}
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
void OpenGLDriver::whenFrameComplete(const std::function<void()>& fn) noexcept {
mFrameCompleteOps.push_back(fn);
}
void OpenGLDriver::whenGpuCommandsComplete(const std::function<void()>& fn) noexcept {
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
mGpuCommandCompleteOps.emplace_back(sync, fn);
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
auto& v = mGpuCommandCompleteOps;
auto it = v.begin();
while (it != v.end()) {
auto const& [sync, fn] = *it;
GLenum const syncStatus = glClientWaitSync(sync, 0, 0u);
switch (syncStatus) {
case GL_TIMEOUT_EXPIRED:
// not ready
++it;
break;
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
// ready
it->second();
glDeleteSync(sync);
it = v.erase(it);
break;
default:
// This should never happen, but is very problematic if it does, as we might leak
// some data depending on what the callback does. However, we clean up our own state.
glDeleteSync(sync);
it = v.erase(it);
break;
}
}
}
#endif
// ------------------------------------------------------------------------------------------------
// Rendering ops
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::tick(int) {
DEBUG_MARKER()
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
executeGpuCommandsCompleteOps();
#endif
executeEveryNowAndThenOps();
getShaderCompilerService().tick();
}
void OpenGLDriver::beginFrame(
UTILS_UNUSED int64_t monotonic_clock_ns,
UTILS_UNUSED int64_t refreshIntervalNs,
UTILS_UNUSED uint32_t frameId) {
PROFILE_MARKER(PROFILE_NAME_BEGINFRAME)
auto& gl = mContext;
insertEventMarker("beginFrame");
mPlatform.beginFrame(monotonic_clock_ns, refreshIntervalNs, frameId);
if (UTILS_UNLIKELY(!mTexturesWithStreamsAttached.empty())) {
OpenGLPlatform& platform = mPlatform;
for (GLTexture const* t : mTexturesWithStreamsAttached) {
assert_invariant(t && t->hwStream);
if (t->hwStream->streamType == StreamType::NATIVE) {
assert_invariant(t->hwStream->stream);
platform.updateTexImage(t->hwStream->stream,
&static_cast<GLStream*>(t->hwStream)->user_thread.timestamp); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
// NOTE: We assume that OpenGLPlatform::updateTexImage() binds the texture on our behalf
gl.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
}
}
}
}
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
DEBUG_MARKER()
}
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
DEBUG_MARKER()
}
void OpenGLDriver::setPresentationTime(int64_t monotonic_clock_ns) {
DEBUG_MARKER()
mPlatform.setPresentationTime(monotonic_clock_ns);
}
void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
PROFILE_MARKER(PROFILE_NAME_ENDFRAME)
#if defined(__EMSCRIPTEN__)
// WebGL builds are single-threaded so users might manipulate various GL state after we're
// done with the frame. We do NOT officially support using Filament in this way, but we can
// at least do some minimal safety things here, such as resetting the VAO to 0.
auto& gl = mContext;
gl.bindVertexArray(nullptr);
for (int unit = OpenGLContext::DUMMY_TEXTURE_BINDING; unit >= 0; unit--) {
gl.bindTexture(unit, GL_TEXTURE_2D, 0, false);
}
gl.disable(GL_CULL_FACE);
gl.depthFunc(GL_LESS);
gl.disable(GL_SCISSOR_TEST);
#endif
//SYSTRACE_NAME("glFinish");
//glFinish();
mPlatform.endFrame(frameId);
insertEventMarker("endFrame");
}
void OpenGLDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size) {
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
GLBufferObject* bo = boh ? handle_cast<GLBufferObject*>(boh) : nullptr;
ds->update(mContext, binding, bo, offset, size);
}
void OpenGLDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
GLTexture* t = th ? handle_cast<GLTexture*>(th) : nullptr;
ds->update(mContext, binding, t, params);
}
void OpenGLDriver::flush(int) {
DEBUG_MARKER()
auto& gl = mContext;
if (!gl.bugs.disable_glFlush) {
glFlush();
}
}
void OpenGLDriver::finish(int) {
DEBUG_MARKER()
glFinish();
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
executeGpuCommandsCompleteOps();
assert_invariant(mGpuCommandCompleteOps.empty());
#endif
executeEveryNowAndThenOps();
// Note: since we executed a glFinish(), all pending tasks should be done
// However, some tasks rely on a separated thread to publish their result (e.g.
// endTimerQuery), so the result could very well not be ready, and the task will
// linger a bit longer, this is only true for mEveryNowAndThenOps tasks.
// The fallout of this is that we can't assert that mEveryNowAndThenOps is empty.
}
UTILS_NOINLINE
void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags clearFlags,
math::float4 const& linearColor, GLfloat depth, GLint stencil) noexcept {
if (any(clearFlags & TargetBufferFlags::COLOR_ALL)) {
mContext.colorMask(GL_TRUE);
}
if (any(clearFlags & TargetBufferFlags::DEPTH)) {
mContext.depthMask(GL_TRUE);
}
if (any(clearFlags & TargetBufferFlags::STENCIL)) {
mContext.stencilMaskSeparate(0xFF, mContext.state.stencil.back.stencilMask);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!mContext.isES2())) {
if (any(clearFlags & TargetBufferFlags::COLOR0)) {
glClearBufferfv(GL_COLOR, 0, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR1)) {
glClearBufferfv(GL_COLOR, 1, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR2)) {
glClearBufferfv(GL_COLOR, 2, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR3)) {
glClearBufferfv(GL_COLOR, 3, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR4)) {
glClearBufferfv(GL_COLOR, 4, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR5)) {
glClearBufferfv(GL_COLOR, 5, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR6)) {
glClearBufferfv(GL_COLOR, 6, linearColor.v);
}
if (any(clearFlags & TargetBufferFlags::COLOR7)) {
glClearBufferfv(GL_COLOR, 7, linearColor.v);
}
if ((clearFlags & TargetBufferFlags::DEPTH_AND_STENCIL) == TargetBufferFlags::DEPTH_AND_STENCIL) {
glClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil);
} else {
if (any(clearFlags & TargetBufferFlags::DEPTH)) {
glClearBufferfv(GL_DEPTH, 0, &depth);
}
if (any(clearFlags & TargetBufferFlags::STENCIL)) {
glClearBufferiv(GL_STENCIL, 0, &stencil);
}
}
} else
#endif
{
GLbitfield mask = 0;
if (any(clearFlags & TargetBufferFlags::COLOR0)) {
glClearColor(linearColor.r, linearColor.g, linearColor.b, linearColor.a);
mask |= GL_COLOR_BUFFER_BIT;
}
if (any(clearFlags & TargetBufferFlags::DEPTH)) {
glClearDepthf(depth);
mask |= GL_DEPTH_BUFFER_BIT;
}
if (any(clearFlags & TargetBufferFlags::STENCIL)) {
glClearStencil(stencil);
mask |= GL_STENCIL_BUFFER_BIT;
}
if (mask) {
glClear(mask);
}
}
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::resolve(
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
DEBUG_MARKER()
GLTexture const* const s = handle_cast<GLTexture*>(src);
GLTexture const* const d = handle_cast<GLTexture*>(dst);
assert_invariant(s);
assert_invariant(d);
FILAMENT_CHECK_PRECONDITION(d->width == s->width && d->height == s->height)
<< "invalid resolve: src and dst sizes don't match";
FILAMENT_CHECK_PRECONDITION(s->samples > 1 && d->samples == 1)
<< "invalid resolve: src.samples=" << +s->samples << ", dst.samples=" << +d->samples;
blit( dst, dstLevel, dstLayer, {},
src, srcLevel, srcLayer, {},
{ d->width, d->height });
}
void OpenGLDriver::blit(
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, uint2 dstOrigin,
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, uint2 srcOrigin,
uint2 size) {
DEBUG_MARKER()
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
assert_invariant(!gl.isES2());
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLTexture* d = handle_cast<GLTexture*>(dst);
GLTexture* s = handle_cast<GLTexture*>(src);
assert_invariant(d);
assert_invariant(s);
ASSERT_PRECONDITION_NON_FATAL(any(d->usage & TextureUsage::BLIT_DST),
"texture doesn't have BLIT_DST");
ASSERT_PRECONDITION_NON_FATAL(any(s->usage & TextureUsage::BLIT_SRC),
"texture doesn't have BLIT_SRC");
ASSERT_PRECONDITION_NON_FATAL(s->format == d->format,
"src and dst texture format don't match");
enum class AttachmentType : GLenum {
COLOR = GL_COLOR_ATTACHMENT0,
DEPTH = GL_DEPTH_ATTACHMENT,
STENCIL = GL_STENCIL_ATTACHMENT,
DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT,
};
auto getFormatType = [](TextureFormat format) -> AttachmentType {
bool const depth = isDepthFormat(format);
bool const stencil = isStencilFormat(format);
if (depth && stencil) return AttachmentType::DEPTH_STENCIL;
if (depth) return AttachmentType::DEPTH;
if (stencil) return AttachmentType::STENCIL;
return AttachmentType::COLOR;
};
AttachmentType const type = getFormatType(d->format);
assert_invariant(type == getFormatType(s->format));
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
GLbitfield mask = {};
switch (type) {
case AttachmentType::COLOR:
mask = GL_COLOR_BUFFER_BIT;
break;
case AttachmentType::DEPTH:
mask = GL_DEPTH_BUFFER_BIT;
break;
case AttachmentType::STENCIL:
mask = GL_STENCIL_BUFFER_BIT;
break;
case AttachmentType::DEPTH_STENCIL:
mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
break;
};
GLuint fbo[2] = {};
glGenFramebuffers(2, fbo);
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]);
switch (d->target) {
case SamplerType::SAMPLER_2D:
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
GL_TEXTURE_2D, d->gl.id, dstLevel);
} else {
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GLenum(type),
GL_RENDERBUFFER, d->gl.id);
}
break;
case SamplerType::SAMPLER_CUBEMAP:
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
GL_TEXTURE_CUBE_MAP_POSITIVE_X + dstLayer, d->gl.id, dstLevel);
break;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
case SamplerType::SAMPLER_3D:
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GLenum(type),
d->gl.id, dstLevel, dstLayer);
break;
case SamplerType::SAMPLER_EXTERNAL:
break;
}
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
switch (s->target) {
case SamplerType::SAMPLER_2D:
if (any(s->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
GL_TEXTURE_2D, s->gl.id, srcLevel);
} else {
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GLenum(type),
GL_RENDERBUFFER, s->gl.id);
}
break;
case SamplerType::SAMPLER_CUBEMAP:
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
GL_TEXTURE_CUBE_MAP_POSITIVE_X + srcLayer, s->gl.id, srcLevel);
break;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
case SamplerType::SAMPLER_3D:
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GLenum(type),
s->gl.id, srcLevel, srcLayer);
break;
case SamplerType::SAMPLER_EXTERNAL:
break;
}
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(
srcOrigin.x, srcOrigin.y, srcOrigin.x + size.x, srcOrigin.y + size.y,
dstOrigin.x, dstOrigin.y, dstOrigin.x + size.x, dstOrigin.y + size.y,
mask, GL_NEAREST);
CHECK_GL_ERROR(utils::slog.e)
gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER);
gl.unbindFramebuffer(GL_READ_FRAMEBUFFER);
glDeleteFramebuffers(2, fbo);
#endif
}
void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
Handle<HwRenderTarget> dst, Viewport dstRect,
Handle<HwRenderTarget> src, Viewport srcRect,
SamplerMagFilter filter) {
// Note: blitDEPRECATED is only used by Renderer::copyFrame
DEBUG_MARKER()
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
assert_invariant(!gl.isES2());
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
<< "blitDEPRECATED only supports COLOR0";
FILAMENT_CHECK_PRECONDITION(
srcRect.left >= 0 && srcRect.bottom >= 0 && dstRect.left >= 0 && dstRect.bottom >= 0)
<< "Source and destination rects must be positive.";
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLenum const glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
// buffer -- this should produce the same output that if we copied from the resolved
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
// is an emulation of that. We cannot use the resolved texture easily because it's not
// actually attached to the RenderTarget. Another implementation would be to do a
// reverse-resolve, but that wouldn't buy us anything.
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
// make sure to catch this scenario.
assert_invariant(d->gl.samples <= 1);
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
// than zero and the formats of draw and read buffers are not identical.
// However, it's not well-defined in the spec what "format" means. So it's difficult
// to have an assert here -- especially when dealing with the default framebuffer
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
// than zero and (...) the source and destination rectangles are not defined with the
// same (X0, Y0) and (X1, Y1) bounds.
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
// happens when blitting from an "implicit" resolve render target (does it work?), so
// to ere on the safe side, we don't allow it.
if (s->gl.samples > 1) {
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
}
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
GL_COLOR_BUFFER_BIT, glFilterMode);
CHECK_GL_ERROR(utils::slog.e)
#endif
}
void OpenGLDriver::bindPipeline(PipelineState const& state) {
DEBUG_MARKER()
auto& gl = mContext;
setRasterState(state.rasterState);
setStencilState(state.stencilState);
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
mValidProgram = useProgram(p);
(*mCurrentPushConstants) = p->getPushConstants();
mCurrentSetLayout = state.pipelineLayout.setLayout;
// TODO: we should validate that the pipeline layout matches the program's
}
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
DEBUG_MARKER()
auto& gl = mContext;
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
// Gracefully do nothing if the render primitive has not been set up.
VertexBufferHandle vb = rp->gl.vertexBufferWithObjects;
if (UTILS_UNLIKELY(!vb)) {
mBoundRenderPrimitive = nullptr;
return;
}
// If necessary, mutate the bindings in the VAO.
gl.bindVertexArray(&rp->gl);
GLVertexBuffer const* const glvb = handle_cast<GLVertexBuffer*>(vb);
updateVertexArrayObject(rp, glvb);
mBoundRenderPrimitive = rp;
}
void OpenGLDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) {
if (UTILS_UNLIKELY(!dsh)) {
mBoundDescriptorSets[set].dsh = dsh;
mInvalidDescriptorSetBindings.set(set, true);
mInvalidDescriptorSetBindingOffsets.set(set, true);
return;
}
// handle_cast<> here also serves to validate the handle (it actually cannot return nullptr)
GLDescriptorSet const* const ds = handle_cast<GLDescriptorSet*>(dsh);
if (ds) {
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
if (mBoundDescriptorSets[set].dsh != dsh) {
// if the descriptor itself changed, we mark this descriptor binding
// invalid -- it will be re-bound at the next draw.
mInvalidDescriptorSetBindings.set(set, true);
} else if (!offsets.empty()) {
// if we reset offsets, we mark the offsets invalid so these descriptors only can
// be re-bound at the next draw.
mInvalidDescriptorSetBindingOffsets.set(set, true);
}
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
// (the data is allocated inside the CommandStream)
mBoundDescriptorSets[set].dsh = dsh;
assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0);
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
mBoundDescriptorSets[set].offsets.data());
}
}
void OpenGLDriver::updateDescriptors(utils::bitset8 invalidDescriptorSets) noexcept {
assert_invariant(mBoundProgram);
auto const offsetOnly = mInvalidDescriptorSetBindingOffsets & ~mInvalidDescriptorSetBindings;
invalidDescriptorSets.forEachSetBit([this, offsetOnly,
&boundDescriptorSets = mBoundDescriptorSets,
&context = mContext,
&boundProgram = *mBoundProgram](size_t set) {
assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT);
auto const& entry = boundDescriptorSets[set];
if (entry.dsh) {
GLDescriptorSet* const ds = handle_cast<GLDescriptorSet*>(entry.dsh);
#ifndef NDEBUG
if (UTILS_UNLIKELY(!offsetOnly[set])) {
// validate that this descriptor-set layout matches the layout set in the pipeline
// we don't need to do the check if only the offset is changing
ds->validate(mHandleAllocator, mCurrentSetLayout[set]);
}
#endif
ds->bind(context, mHandleAllocator, boundProgram,
set, entry.offsets.data(), offsetOnly[set]);
}
});
mInvalidDescriptorSetBindings.clear();
mInvalidDescriptorSetBindingOffsets.clear();
}
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
DEBUG_MARKER()
assert_invariant(!mContext.isES2());
assert_invariant(mBoundRenderPrimitive);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!mValidProgram)) {
return;
}
#endif
assert_invariant(mBoundProgram);
assert_invariant(mValidProgram);
// When the program changes, we might have to rebind all or some descriptors
auto const invalidDescriptorSets =
mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets;
if (UTILS_UNLIKELY(invalidDescriptorSets.any())) {
updateDescriptors(invalidDescriptorSets);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift),
(GLsizei)instanceCount);
#endif
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
#endif
}
// This is the ES2 version of draw2().
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
DEBUG_MARKER()
assert_invariant(mContext.isES2());
assert_invariant(mBoundRenderPrimitive);
#if FILAMENT_ENABLE_MATDBG
if (UTILS_UNLIKELY(!mValidProgram)) {
return;
}
#endif
assert_invariant(mBoundProgram);
assert_invariant(mValidProgram);
// When the program changes, we might have to rebind all or some descriptors
auto const invalidDescriptorSets =
mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets;
if (UTILS_UNLIKELY(invalidDescriptorSets.any())) {
updateDescriptors(invalidDescriptorSets);
}
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
assert_invariant(instanceCount == 1);
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift));
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
#endif
}
void OpenGLDriver::scissor(Viewport scissor) {
DEBUG_MARKER()
setScissor(scissor);
}
void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
DEBUG_MARKER()
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
state.primitiveType = rp->type;
state.vertexBufferInfo = rp->vbih;
bindPipeline(state);
bindRenderPrimitive(rph);
if (UTILS_UNLIKELY(mContext.isES2())) {
draw2GLES2(indexOffset, indexCount, instanceCount);
} else {
draw2(indexOffset, indexCount, instanceCount);
}
}
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
DEBUG_MARKER()
getShaderCompilerService().tick();
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(program);
bool const success = useProgram(p);
if (UTILS_UNLIKELY(!success)) {
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
// is invalid. The shader compile error has already been dumped to the console at this
// point, so it's fine to simply return early.
return;
}
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
#if defined(__ANDROID__)
// on Android, GLES3.1 and above entry-points are defined in glext
// (this is temporary, until we phase-out API < 21)
using glext::glDispatchCompute;
#endif
glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
#endif // BACKEND_OPENGL_LEVEL_GLES31
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
#else
CHECK_GL_ERROR(utils::slog.e)
#endif
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<OpenGLDriver>;
} // namespace filament::backend
#if defined(__clang__)
#pragma clang diagnostic pop
#endif