/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "gles-context.h"
#include <dali/integration-api/adaptor-framework/render-surface-interface.h>
+#include <dali/integration-api/debug.h>
#include <dali/integration-api/gl-abstraction.h>
#include <dali/integration-api/gl-defines.h>
#include <dali/internal/graphics/common/graphics-interface.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <map>
+#include <unordered_map>
namespace Dali::Graphics::GLES
{
* that VertexInputState has been set correctly for the pipeline.
*
*/
- void BindProgramVAO(GLES::ProgramImpl* program, const VertexInputState& vertexInputState)
+ void BindProgramVAO(const GLES::ProgramImpl* program, const VertexInputState& vertexInputState)
{
+ // Calculate attributes location hash unordered.
+ std::size_t hash = 0;
+ for(const auto& attr : vertexInputState.attributes)
+ {
+ hash ^= std::hash<uint32_t>{}(attr.location);
+ }
+
auto& gl = *mController.GetGL();
auto iter = mProgramVAOMap.find(program);
if(iter != mProgramVAOMap.end())
{
- if(mProgramVAOCurrentState != iter->second)
+ auto attributeIter = iter->second.find(hash);
+ if(attributeIter != iter->second.end())
{
- mProgramVAOCurrentState = iter->second;
- gl.BindVertexArray(iter->second);
+ if(mProgramVAOCurrentState != attributeIter->second)
+ {
+ mProgramVAOCurrentState = attributeIter->second;
+ gl.BindVertexArray(attributeIter->second);
+
+ // Binding VAO seems to reset the index buffer binding so the cache must be reset
+ mGlStateCache.mBoundElementArrayBufferId = 0;
+ }
+ return;
}
- return;
}
uint32_t vao;
gl.GenVertexArrays(1, &vao);
gl.BindVertexArray(vao);
- mProgramVAOMap[program] = vao;
+
+ // Binding VAO seems to reset the index buffer binding so the cache must be reset
+ mGlStateCache.mBoundElementArrayBufferId = 0;
+
+ mProgramVAOMap[program][hash] = vao;
for(const auto& attr : vertexInputState.attributes)
{
gl.EnableVertexAttribArray(attr.location);
memset(&mGlStateCache.mBoundTextureId, 0, sizeof(mGlStateCache.mBoundTextureId));
mGlStateCache.mFrameBufferStateCache.Reset();
+
+ GLint maxTextures;
+ gl.GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextures);
+ DALI_LOG_RELEASE_INFO("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d\n", maxTextures);
}
/**
const GLES::RenderPass* mCurrentRenderPass{nullptr};
// Each context must have own VAOs as they cannot be shared
- std::map<GLES::ProgramImpl*, uint32_t> mProgramVAOMap; ///< GL program-VAO map
- uint32_t mProgramVAOCurrentState{0u}; ///< Currently bound VAO
- GLStateCache mGlStateCache{}; ///< GL status cache
+ std::unordered_map<const GLES::ProgramImpl*, std::map<std::size_t, uint32_t>> mProgramVAOMap; ///< GL program-VAO map
+ uint32_t mProgramVAOCurrentState{0u}; ///< Currently bound VAO
+ GLStateCache mGlStateCache{}; ///< GL status cache
- bool mGlContextCreated{false}; ///< True if the OpenGL context has been created
+ bool mGlContextCreated{false}; ///< True if the OpenGL context has been created
+ bool mVertexBuffersChanged{true}; ///< True if BindVertexBuffers changed any buffer bindings
EGLContext mNativeDrawContext{0u}; ///< Native rendering EGL context compatible with window context
newProgram = static_cast<const GLES::Program*>(mImpl->mNewPipeline->GetCreateInfo().programState->program);
}
+ if(!currentProgram && !newProgram)
+ {
+ // Early out if we have no program for this pipeline.
+ DALI_LOG_ERROR("No program defined for pipeline\n");
+ return;
+ }
+
+ // If this draw uses a different pipeline _AND_ the pipeline has a different GL Program,
+ // Then bind the new program. Ensure vertex atrributes are set.
+
+ bool programChanged = false;
if(mImpl->mNewPipeline && mImpl->mCurrentPipeline != mImpl->mNewPipeline)
{
if(!currentProgram || currentProgram->GetImplementation()->GetGlProgram() != newProgram->GetImplementation()->GetGlProgram())
{
mImpl->mNewPipeline->Bind(newProgram->GetImplementation()->GetGlProgram());
+ programChanged = true;
}
// Blend state
// Map binding# to sampler location
const auto& reflection = !newProgram ? currentProgram->GetReflection() : newProgram->GetReflection();
const auto& samplers = reflection.GetSamplers();
+
+ uint32_t currentSampler = 0;
+ uint32_t currentElement = 0;
+
+ // @warning Assume that binding.binding is strictly linear in the same order as mCurrentTextureBindings
+ // elements. This avoids having to sort the bindings.
for(const auto& binding : mImpl->mCurrentTextureBindings)
{
+ if(currentSampler >= samplers.size())
+ {
+ // Don't bind more textures than there are active samplers.
+ break;
+ }
+
auto texture = const_cast<GLES::Texture*>(static_cast<const GLES::Texture*>(binding.texture));
// Texture may not have been initialized yet...(tbm_surface timing issue?)
if(!texture->GetGLTexture())
{
- // Attempt to reinitialize
- // @todo need to put this somewhere else where it isn't const.
- // Maybe post it bac/k on end of initialize queue if initialization fails?
texture->InitializeResource();
}
// Warning, this may cause glWaitSync to occur on the GPU.
dependencyChecker.CheckNeedsSync(this, texture);
-
texture->Bind(binding);
+ texture->Prepare();
- texture->Prepare(); // @todo also non-const.
-
- if(binding.binding < samplers.size()) // binding maps to texture unit. (texture bindings should also be in binding order)
+ if(programChanged)
{
- // Offset is set to the lexical offset within the frag shader, map it to the texture unit
- // @todo Explicitly set the texture unit through the graphics interface
- gl.Uniform1i(samplers[binding.binding].location, samplers[binding.binding].offset);
+ // @warning Assume that location of array elements is sequential.
+ // @warning GL does not guarantee this, but in practice, it is.
+ gl.Uniform1i(samplers[currentSampler].location + currentElement,
+ samplers[currentSampler].offset + currentElement);
+ ++currentElement;
+ if(currentElement >= samplers[currentSampler].elementCount)
+ {
+ ++currentSampler;
+ currentElement = 0;
+ }
}
}
- // for each attribute bind vertices
-
const auto& pipelineState = mImpl->mNewPipeline ? mImpl->mNewPipeline->GetCreateInfo() : mImpl->mCurrentPipeline->GetCreateInfo();
const auto& vertexInputState = pipelineState.vertexInputState;
- if(hasGLES3)
+ // for each attribute bind vertices, unless the pipeline+buffer is the same
+ if(programChanged || mImpl->mVertexBuffersChanged)
{
- mImpl->BindProgramVAO(static_cast<const GLES::Program*>(pipelineState.programState->program)->GetImplementation(), *vertexInputState);
- }
-
- for(const auto& attr : vertexInputState->attributes)
- {
- // Enable location
- if(!hasGLES3)
+ if(hasGLES3)
{
- mImpl->SetVertexAttributeLocation(attr.location, true);
+ mImpl->BindProgramVAO(static_cast<const GLES::Program*>(pipelineState.programState->program)->GetImplementation(), *vertexInputState);
}
- const auto& bufferSlot = mImpl->mCurrentVertexBufferBindings[attr.binding];
- const auto& bufferBinding = vertexInputState->bufferBindings[attr.binding];
+ for(const auto& attr : vertexInputState->attributes)
+ {
+ // Enable location
+ if(!hasGLES3)
+ {
+ mImpl->SetVertexAttributeLocation(attr.location, true);
+ }
+
+ const auto& bufferSlot = mImpl->mCurrentVertexBufferBindings[attr.binding];
+ const auto& bufferBinding = vertexInputState->bufferBindings[attr.binding];
- auto glesBuffer = bufferSlot.buffer->GetGLBuffer();
+ auto glesBuffer = bufferSlot.buffer->GetGLBuffer();
- // Bind buffer
- BindBuffer(GL_ARRAY_BUFFER, glesBuffer);
+ BindBuffer(GL_ARRAY_BUFFER, glesBuffer); // Cached
- gl.VertexAttribPointer(attr.location,
- GLVertexFormat(attr.format).size,
- GLVertexFormat(attr.format).format,
- GL_FALSE,
- bufferBinding.stride,
- reinterpret_cast<void*>(attr.offset));
+ if(attr.format == VertexInputFormat::FLOAT ||
+ attr.format == VertexInputFormat::FVECTOR2 ||
+ attr.format == VertexInputFormat::FVECTOR3 ||
+ attr.format == VertexInputFormat::FVECTOR4)
+ {
+ gl.VertexAttribPointer(attr.location, // Not cached...
+ GLVertexFormat(attr.format).size,
+ GLVertexFormat(attr.format).format,
+ GL_FALSE,
+ bufferBinding.stride,
+ reinterpret_cast<void*>(attr.offset));
+ }
+ else
+ {
+ gl.VertexAttribIPointer(attr.location,
+ GLVertexFormat(attr.format).size,
+ GLVertexFormat(attr.format).format,
+ bufferBinding.stride,
+ reinterpret_cast<void*>(attr.offset));
+ }
+
+ if(hasGLES3)
+ {
+ switch(bufferBinding.inputRate)
+ {
+ case Graphics::VertexInputRate::PER_VERTEX:
+ {
+ gl.VertexAttribDivisor(attr.location, 0);
+ break;
+ }
+ case Graphics::VertexInputRate::PER_INSTANCE:
+ {
+ //@todo Get actual instance rate...
+ gl.VertexAttribDivisor(attr.location, 1);
+ break;
+ }
+ }
+ }
+ }
}
// Resolve topology
const auto& ia = pipelineState.inputAssemblyState;
- // Bind uniforms
-
// Resolve draw call
switch(drawCall.type)
{
mImpl->FlushVertexAttributeLocations();
}
- //@todo Wait if textures need syncing
- gl.DrawArrays(GLESTopology(ia->topology),
- drawCall.draw.firstVertex,
- drawCall.draw.vertexCount);
+ if(drawCall.draw.instanceCount == 0)
+ {
+ gl.DrawArrays(GLESTopology(ia->topology),
+ drawCall.draw.firstVertex,
+ drawCall.draw.vertexCount);
+ }
+ else
+ {
+ gl.DrawArraysInstanced(GLESTopology(ia->topology),
+ drawCall.draw.firstVertex,
+ drawCall.draw.vertexCount,
+ drawCall.draw.instanceCount);
+ }
break;
}
case DrawCallDescriptor::Type::DRAW_INDEXED:
}
auto indexBufferFormat = GLIndexFormat(binding.format).format;
- gl.DrawElements(GLESTopology(ia->topology),
- drawCall.drawIndexed.indexCount,
- indexBufferFormat,
- reinterpret_cast<void*>(binding.offset));
+ if(drawCall.drawIndexed.instanceCount == 0)
+ {
+ gl.DrawElements(GLESTopology(ia->topology),
+ drawCall.drawIndexed.indexCount,
+ indexBufferFormat,
+ reinterpret_cast<void*>(binding.offset));
+ }
+ else
+ {
+ gl.DrawElementsInstanced(GLESTopology(ia->topology),
+ drawCall.drawIndexed.indexCount,
+ indexBufferFormat,
+ reinterpret_cast<void*>(binding.offset),
+ drawCall.drawIndexed.instanceCount);
+ }
break;
}
case DrawCallDescriptor::Type::DRAW_INDEXED_INDIRECT:
mImpl->mCurrentVertexBufferBindings.resize(count);
}
// Copy only set slots
- std::copy_if(bindings, bindings + count, mImpl->mCurrentVertexBufferBindings.begin(), [](auto& item) {
- return (nullptr != item.buffer);
- });
+ mImpl->mVertexBuffersChanged = false;
+ auto toIter = mImpl->mCurrentVertexBufferBindings.begin();
+ for(auto fromIter = bindings, end = bindings + count; fromIter != end; ++fromIter)
+ {
+ if(fromIter->buffer != nullptr)
+ {
+ if(toIter->buffer != fromIter->buffer || toIter->offset != fromIter->offset)
+ {
+ mImpl->mVertexBuffersChanged = true;
+ }
+ *toIter++ = *fromIter;
+ }
+ }
}
void Context::BindIndexBuffer(const IndexBufferBindingDescriptor& indexBufferBinding)
mImpl->mCurrentStandaloneUBOBinding = standaloneBindings;
}
- if(uboCount >= mImpl->mCurrentUBOBindings.size())
+ if(uboCount && uboCount > mImpl->mCurrentUBOBindings.size())
{
- mImpl->mCurrentUBOBindings.resize(uboCount + 1);
+ mImpl->mCurrentUBOBindings.resize(uboCount);
}
auto it = uboBindings;
{
mImpl->mCurrentUBOBindings[i] = *it;
}
+ ++it;
}
}
{
ResolveStandaloneUniforms();
}
+ if(!mImpl->mCurrentUBOBindings.empty())
+ {
+ ResolveGpuUniformBuffers();
+ }
+}
+
+void Context::ResolveGpuUniformBuffers()
+{
+ auto& gl = *mImpl->mController.GetGL();
+ auto i = 0u;
+ for(auto& binding : mImpl->mCurrentUBOBindings)
+ {
+ gl.BindBufferRange(GL_UNIFORM_BUFFER, i++, binding.buffer->GetGLBuffer(), GLintptr(binding.offset), binding.dataSize);
+ }
}
void Context::ResolveStandaloneUniforms()
void Context::ClearState()
{
mImpl->mCurrentTextureBindings.clear();
+ mImpl->mCurrentUBOBindings.clear();
}
void Context::ColorMask(bool enabled)
gl.GenerateMipmap(target);
}
-void Context::BindBuffer(GLenum target, uint32_t bufferId)
+bool Context::BindBuffer(GLenum target, uint32_t bufferId)
{
- if(mImpl->mGlStateCache.mBoundArrayBufferId != bufferId)
+ switch(target)
{
- mImpl->mGlStateCache.mBoundArrayBufferId = bufferId;
-
- auto& gl = *mImpl->mController.GetGL();
- gl.BindBuffer(target, bufferId);
+ case GL_ARRAY_BUFFER:
+ {
+ if(mImpl->mGlStateCache.mBoundArrayBufferId == bufferId)
+ {
+ return false;
+ }
+ mImpl->mGlStateCache.mBoundArrayBufferId = bufferId;
+ break;
+ }
+ case GL_ELEMENT_ARRAY_BUFFER:
+ {
+ if(mImpl->mGlStateCache.mBoundElementArrayBufferId == bufferId)
+ {
+ return false;
+ }
+ mImpl->mGlStateCache.mBoundElementArrayBufferId = bufferId;
+ break;
+ }
}
+
+ // Cache miss. Bind buffer.
+ auto& gl = *mImpl->mController.GetGL();
+ gl.BindBuffer(target, bufferId);
+ return true;
}
void Context::DrawBuffers(uint32_t count, const GLenum* buffers)
{
mImpl->mCurrentPipeline = nullptr;
}
+
+ // Remove cached VAO map
+ auto* gl = mImpl->mController.GetGL();
+ if(gl)
+ {
+ const auto* program = pipeline->GetCreateInfo().programState->program;
+ if(program)
+ {
+ const auto* programImpl = static_cast<const GLES::Program*>(program)->GetImplementation();
+ if(programImpl)
+ {
+ auto iter = mImpl->mProgramVAOMap.find(programImpl);
+ if(iter != mImpl->mProgramVAOMap.end())
+ {
+ for(auto& attributeHashPair : iter->second)
+ {
+ auto vao = attributeHashPair.second;
+ gl->DeleteVertexArrays(1, &vao);
+ if(mImpl->mProgramVAOCurrentState == vao)
+ {
+ mImpl->mProgramVAOCurrentState = 0u;
+ }
+ }
+ mImpl->mProgramVAOMap.erase(iter);
+ }
+ }
+ }
+ }
}
void Context::PrepareForNativeRendering()
if(!mImpl->mNativeDrawContext)
{
EGLint configId{0u};
- EGLint size{0u};
- eglGetConfigs(display, nullptr, 0, &size);
- std::vector<EGLConfig> configs;
- configs.resize(size);
- eglGetConfigs(display, configs.data(), configs.size(), &size);
+ eglQueryContext(display, mImpl->mController.GetSharedContext(), EGL_CONFIG_ID, &configId);
+
+ EGLint configAttribs[3];
+ configAttribs[0] = EGL_CONFIG_ID;
+ configAttribs[1] = configId;
+ configAttribs[2] = EGL_NONE;
- eglQueryContext(display, context, EGL_CONFIG_ID, &configId);
+ EGLConfig config;
+ EGLint numConfigs;
+ if(eglChooseConfig(display, configAttribs, &config, 1, &numConfigs) != EGL_TRUE)
+ {
+ DALI_LOG_ERROR("eglChooseConfig failed!\n");
+ return;
+ }
auto version = int(mImpl->mController.GetGLESVersion());
attribs.push_back(version % 10);
attribs.push_back(EGL_NONE);
- mImpl->mNativeDrawContext = eglCreateContext(display, configs[configId], mImpl->mController.GetSharedContext(), attribs.data());
+ mImpl->mNativeDrawContext = eglCreateContext(display, config, mImpl->mController.GetSharedContext(), attribs.data());
+ if(mImpl->mNativeDrawContext == EGL_NO_CONTEXT)
+ {
+ DALI_LOG_ERROR("eglCreateContext failed!\n");
+ return;
+ }
}
eglMakeCurrent(display, drawSurface, readSurface, mImpl->mNativeDrawContext);