diff --git a/CMakeLists.txt b/CMakeLists.txt index accb47a3..0d718f8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project("MobileGL") enable_language(CXX) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -16,7 +16,7 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++") set(CMAKE_ANDROID_STL_TYPE c++_static) -set(CMAKE_BUILD_TYPE Release) +#set(CMAKE_BUILD_TYPE Release) set(PROFILING OFF) @@ -118,4 +118,4 @@ if (PROFILING) add_library(perfetto STATIC ${CMAKE_SOURCE_DIR}/3rdparty/perfetto/sdk/perfetto.cc) target_link_libraries(MobileGL perfetto ${CMAKE_THREAD_LIBS_INIT}) target_compile_definitions(MobileGL PUBLIC PROFILING=1) -endif() \ No newline at end of file +endif() diff --git a/MG/Global.h b/MG/Global.h index 61adf95d..c4226369 100644 --- a/MG/Global.h +++ b/MG/Global.h @@ -27,7 +27,7 @@ namespace MG_Global { } namespace Common { - inline const int LogLevel = MG_Constants::Common::LOG_LEVEL_INFO; + inline constexpr int LogLevel = MG_Constants::Common::LOG_LEVEL_INFO; #ifdef __ANDROID__ inline const char* LOG_FILE_PATH = "/sdcard/MG/latest.log"; diff --git a/MG/Includes.h b/MG/Includes.h index a353ec19..7b4471c0 100644 --- a/MG/Includes.h +++ b/MG/Includes.h @@ -102,6 +102,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,7 @@ #include #include #include +#include #include "GLES/gl32.h" #include "MG_Include/UncertainBool.hpp" diff --git a/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.cpp b/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.cpp index f6d567d9..1ee4a7ed 100644 --- a/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.cpp +++ b/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.cpp @@ -38,8 +38,20 @@ namespace MG_GL::GL { } void BindBuffer(GLenum target, GLuint buffer) { - MG_Util::Debug::LogD("glBindBuffer, target: %d, buffer: %d", target, buffer); - if (buffer != 0 && !MG_State::ValidateBufferHandle(buffer)) { + MG_Util::Debug::LogD("glBindBuffer, target: %s, buffer: %d", MG_Util::Debug::GLEnumToString(target), buffer); + if (buffer != 0 && + MG_State::ValidateGeneratedName(buffer) && + !MG_State::ValidateAllocatedBufferHandle(buffer)) { + MG_Util::Debug::LogD("Actually creating buffer: %u", buffer); + GLenum result = MG_State::CreateBuffer(buffer); + if (result != GL_NO_ERROR) { + MG_State::SetError(result); + MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result)); + return; + } + } + + if (buffer != 0 && !MG_State::ValidateAllocatedBufferHandle(buffer)) { MG_State::SetError(GL_INVALID_VALUE); MG_Util::Debug::LogE("Invalid buffer handle: %u", buffer); return; @@ -52,8 +64,8 @@ namespace MG_GL::GL { } void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) { - MG_Util::Debug::LogD("glBufferData, target: %d, size: %zd, data: %p, usage: %d", - target, size, data, usage); + MG_Util::Debug::LogD("glBufferData, target: %s, size: %zd, data: %p, usage: %s", + MG_Util::Debug::GLEnumToString(target), size, data, MG_Util::Debug::GLEnumToString(usage)); GLenum result = MG_State::CommitBufferStorage(target, size, data, usage); if (result == GL_NO_ERROR) return; @@ -80,9 +92,9 @@ namespace MG_GL::GL { return; } - GLenum result = MG_State::CreateBuffers(n, buffers); + GLenum result = MG_State::GenBufferNames(n, buffers); if (result == GL_NO_ERROR) { - MG_Util::Debug::LogD("Generated buffers:"); + MG_Util::Debug::LogD("Generated buffer names:"); for (GLsizei i = 0; i < n; ++i) { MG_Util::Debug::LogD(" Buffer[%d] = %u", i, buffers[i]); } @@ -100,9 +112,20 @@ namespace MG_GL::GL { return GL_FALSE; } - bool isValid = MG_State::ValidateBufferHandle(buffer); + bool isValid = MG_State::ValidateAllocatedBufferHandle(buffer); // Should we report gl error here or in MG_State? MG_Util::Debug::LogD("Buffer %u is %s", buffer, isValid ? "valid" : "invalid"); return isValid ? GL_TRUE : GL_FALSE; } + + void DeleteBuffers(GLsizei n, const GLuint *buffers) { + MG_Util::Debug::LogD("glDeleteBuffers, n: %d, buffers: %p", n, buffers); + + GLenum result = MG_State::DeleteBuffers(n, buffers); + if (result == GL_NO_ERROR) + return; + + MG_State::SetError(result); + MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result)); + } } \ No newline at end of file diff --git a/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.h b/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.h index 4ac655eb..a23fde71 100644 --- a/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.h +++ b/MG/MG_GL/Implementations/GL/Buffer/GL_Buffer.h @@ -14,6 +14,7 @@ namespace MG_GL::GL { void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage); void GetBufferParameteriv(GLenum target, GLenum pname, GLint* params); void GenBuffers(GLsizei n, GLuint* buffers); + void DeleteBuffers(GLsizei n, const GLuint *buffers); GLboolean IsBuffer(GLuint buffer); } diff --git a/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp b/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp index 8ab473e6..40934450 100644 --- a/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp +++ b/MG/MG_GL/Implementations/GL/Drawing/GL_Drawing.cpp @@ -8,6 +8,298 @@ #include "../../../../Includes.h" namespace MG_GL::GL { + template + using unordered_map = ankerl::unordered_dense::map; + + void NormalizePixelFormat(GLenum internalFormat, GLenum type, GLenum format, GLenum* outInternalFormat, GLenum* outType, GLenum* outFormat) { +// if (format && *format == GL_BGRA) +// *format = GL_RGBA; + switch (internalFormat) { + case GL_DEPTH_COMPONENT16: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_SHORT; + if (outFormat) + *outFormat = format; + break; + + case GL_DEPTH_COMPONENT24: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_INT; + if (outFormat) + *outFormat = format; + break; + + case GL_DEPTH_COMPONENT32: + if (outInternalFormat) + *outInternalFormat = GL_DEPTH_COMPONENT32F; + if (outType) + *outType = GL_UNSIGNED_INT; + if (outFormat) + *outFormat = format; + break; + + case GL_DEPTH_COMPONENT32F: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_FLOAT; + if (outFormat) + *outFormat = format; + break; + + case GL_DEPTH_COMPONENT: + MG_Util::Debug::LogD("Find GL_DEPTH_COMPONENT: internalFormat: %s, format: %s, type: %s", + MG_Util::Debug::GLEnumToString(internalFormat), MG_Util::Debug::GLEnumToString(format), MG_Util::Debug::GLEnumToString(type)); + + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_INT; + if (outFormat) + *outFormat = format; + break; + + case GL_DEPTH_STENCIL: + if (outInternalFormat) + *outInternalFormat = GL_DEPTH32F_STENCIL8; + if (outType) + *outType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + if (outFormat) + *outFormat = format; + break; + + case GL_RGB10_A2: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_INT_2_10_10_10_REV; + if (outFormat) + *outFormat = format; + break; + + case GL_RGB5_A1: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_SHORT_5_5_5_1; + if (outFormat) + *outFormat = format; + break; + + case GL_COMPRESSED_RED_RGTC1: + case GL_COMPRESSED_RG_RGTC2: + MG_Util::Debug::LogD("GL_COMPRESSED_RED_RGTC1 or GL_COMPRESSED_RG_RGTC2 is not supported!"); + break; + + case GL_SRGB8: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_BYTE; + if (outFormat) + *outFormat = format; + break; + + case GL_RGBA32F: + case GL_RGB32F: + case GL_RG32F: + case GL_R32F: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_FLOAT; + if (outFormat) + *outFormat = format; + break; + + case GL_RGB9_E5: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_INT_5_9_9_9_REV; + if (outFormat) + *outFormat = format; + break; + + case GL_R11F_G11F_B10F: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_INT_10F_11F_11F_REV; + if (outFormat) + *outFormat = GL_RGB; + break; + + case GL_RGBA32UI: + case GL_RGB32UI: + case GL_RG32UI: + case GL_R32UI: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_INT; + if (outFormat) + *outFormat = format; + break; + + case GL_RGBA32I: + case GL_RGB32I: + case GL_RG32I: + case GL_R32I: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_INT; + if (outFormat) + *outFormat = format; + break; + + case GL_RGBA16: { +// if (!checked_rgba16) { +// support_rgba16 = check_rgba16(); +// checked_rgba16 = true; +// } +// if (support_rgba16) { +// if(type) +// *type = GL_UNSIGNED_SHORT; +// } else { +// *internal_format = GL_RGBA16F; +// if(type) +// *type = GL_FLOAT; +// } + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_FLOAT; + if (outFormat) + *outFormat = format; + break; + } + case GL_RGBA8: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_BYTE; + if (outFormat) + *outFormat = GL_RGBA; + break; + + case GL_RGBA: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_BYTE; + if (outFormat) + *outFormat = GL_RGBA; + break; + + case GL_RGBA16F: + case GL_R16F: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_HALF_FLOAT; + if (outFormat) + *outFormat = format; + break; + + case GL_R16: + if (outInternalFormat) + *outInternalFormat = GL_R16F; + if (outType) + *outType = GL_FLOAT; + if (outFormat) + *outFormat = format; + break; + + case GL_RGB16: + if (outInternalFormat) + *outInternalFormat = GL_RGB16F; + if (outType) + *outType = GL_HALF_FLOAT; + if (outFormat) + *outFormat = GL_RGB; + break; + + case GL_RGB16F: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_HALF_FLOAT; + if (outFormat) + *outFormat = GL_RGB; + break; + + case GL_RG16: + case GL_RG16F: + if (outInternalFormat) + *outInternalFormat = GL_RG16F; + if (outType) + *outType = GL_HALF_FLOAT; + if (outFormat) + *outFormat = GL_RG; + break; + + case GL_R8: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_BYTE; + if (outFormat) + *outFormat = GL_RED; + break; + case GL_R8UI: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_UNSIGNED_BYTE; + if (outFormat) + *outFormat = GL_RED_INTEGER; + break; + + case GL_RGB8_SNORM: + case GL_RGBA8_SNORM: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_BYTE; + if (outFormat) + *outFormat = format; + break; + case GL_RGB8: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType && type != GL_UNSIGNED_BYTE) + *outType = GL_UNSIGNED_BYTE; + if (outFormat) + *outFormat = GL_RGB; + break; + case GL_RGBA16_SNORM: + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = GL_SHORT; + if (outFormat) + *outFormat = format; + break; + default: + MG_Util::Debug::LogD("NormalizePixelFormat: no conversion"); + + if (outInternalFormat) + *outInternalFormat = internalFormat; + if (outType) + *outType = type; + if (outFormat) + *outFormat = format; + break; + } + } + + std::string processOutColorLocations(const std::string& glslCode) { const static std::regex pattern(R"(\n(out highp vec4 outColor)(\d+);)"); const std::string replacement = "\nlayout(location=$2) $1$2;"; @@ -79,19 +371,19 @@ namespace MG_GL::GL { return result; } - static std::unordered_map s_textureMap; - static std::unordered_map s_vaoMap; - static std::unordered_map s_bufferMap; - static std::unordered_map s_programMap; - static std::unordered_map s_framebufferMap; - struct MipLevelInfo { - GLenum internalFormat; - GLsizei width; - GLsizei height; - GLenum format; - GLenum type; + static unordered_map s_textureMap; + static unordered_map s_vaoMap; + static unordered_map s_bufferMap; + static unordered_map s_programMap; + static unordered_map s_framebufferMap; + + struct TexParamCache { + std::unordered_map lastInt; + std::unordered_map lastFloat; }; + static std::unordered_map cache; + void CheckGLESError() { while (GLenum err = ::GLES::glGetError() != GL_NO_ERROR) { MG_Util::Debug::LogE("-> GLES Error: %s", MG_Util::Debug::GLEnumToString(err)); @@ -99,124 +391,360 @@ namespace MG_GL::GL { } #define CallAndCheck(operation) MG_Util::Debug::LogD("GLES call: %s", #operation); operation CheckGLESError(); - static std::unordered_map> s_textureLevelUploaded; +// static std::unordered_map> s_textureLevelUploaded; void SyncAllTexturesToGLES(TextureState* textureState) { MG_Util::Debug::LogD("Syncing all textures to GLES..."); for (auto& [mgTexId, texObj] : textureState->textures) { - if (!texObj.generated) continue; + if (!texObj.generated) + continue; + GLuint glTexId = 0; if (s_textureMap.find(mgTexId) == s_textureMap.end()) { - GLuint glTexId; CallAndCheck(::GLES::glGenTextures(1, &glTexId);) s_textureMap[mgTexId] = glTexId; - GLenum target = texObj.target; - CallAndCheck(::GLES::glBindTexture(target, glTexId);) - - for (auto& [pname, param] : texObj.params.texPropertiesInt) { - CallAndCheck(::GLES::glTexParameteri(target, pname, param);) - } - for (auto& [pname, param] : texObj.params.texPropertiesFloat) { - CallAndCheck(::GLES::glTexParameterf(target, pname, param);) - } - - for (auto& [level, mip] : texObj.params.mipmapData) { - const void* data = !mip.pixelData.empty() ? mip.pixelData.data() : nullptr; - switch (target) { - case GL_TEXTURE_2D: - CallAndCheck(::GLES::glTexImage2D( - target, level, mip.internalFormat, - mip.width, mip.height, 0, - mip.format, mip.type, data - );) - s_textureLevelUploaded[mgTexId][level] = true; - MG_Util::Debug::LogD("Initial upload texture %u level %d (size=%zu)", mgTexId, level, mip.pixelData.size()); - break; - default: - MG_Util::Debug::LogE("Unsupported target: %s", MG_Util::Debug::GLEnumToString(target)); - } - } - MG_Util::Debug::LogD("Created and synced new GLES texture %u (MobileGL ID)", mgTexId); + MG_Util::Debug::LogD("Created new GLES texture %u (MobileGL ID)", mgTexId); } else { - GLuint glTexId = s_textureMap[mgTexId]; - GLenum target = texObj.target; - CallAndCheck(::GLES::glBindTexture(target, glTexId);) + glTexId = s_textureMap[mgTexId]; + } + GLenum target = texObj.target; + CallAndCheck(::GLES::glBindTexture(target, glTexId);) + TexParamCache& entry = cache[&texObj]; + for (auto& [pname, param] : texObj.params.texPropertiesInt) { + auto it = entry.lastInt.find(pname); + if (it == entry.lastInt.end() || it->second != param) { + CallAndCheck(::GLES::glTexParameteri(target, pname, param);) + entry.lastInt[pname] = param; + } + } - for (auto& [level, mip] : texObj.params.mipmapData) { - if (!s_textureLevelUploaded[mgTexId][level]) { - bool levelInitialized = s_textureLevelUploaded[mgTexId].count(level); - const void* data = !mip.pixelData.empty() ? mip.pixelData.data() : nullptr; + for (auto& [pname, param] : texObj.params.texPropertiesFloat) { + auto it = entry.lastFloat.find(pname); + if (it == entry.lastFloat.end() || it->second != param) { + CallAndCheck(::GLES::glTexParameterf(target, pname, param);) + entry.lastFloat[pname] = param; + } + } - switch (target) { - case GL_TEXTURE_2D: - if (levelInitialized) { - CallAndCheck(::GLES::glTexSubImage2D( - target, level, - 0, 0, - mip.width, mip.height, - mip.format, mip.type, data - );) - s_textureLevelUploaded[mgTexId][level] = true; - MG_Util::Debug::LogD("Updated texture %u level %d with glTexSubImage2D", mgTexId, level); - } else { - CallAndCheck(::GLES::glTexImage2D( - target, level, mip.internalFormat, - mip.width, mip.height, 0, - mip.format, mip.type, data - );) - s_textureLevelUploaded[mgTexId][level] = true; - MG_Util::Debug::LogD("Initialized texture %u level %d with glTexImage2D", mgTexId, level); - } - break; - default: - MG_Util::Debug::LogE("Unsupported target: %s", MG_Util::Debug::GLEnumToString(target)); - } + for (auto& [level, mip] : texObj.params.mipmapData) { + if (!mip.dirty) + continue; + mip.dirty = false; + const void* data = !mip.pixelData.empty() ? mip.pixelData.data() : nullptr; + switch (target) { + case GL_TEXTURE_2D: { + GLenum internalFormat = 0, type = 0, format = 0; + NormalizePixelFormat(mip.internalFormat, mip.type, mip.format, &internalFormat, &type, &format); + + CallAndCheck(::GLES::glTexImage2D( + target, level, internalFormat, + mip.width, mip.height, 0, + format, type, data + );) + MG_Util::Debug::LogD("Initial upload texture %u level %d (size=%zu)", + mgTexId, level, mip.pixelData.size()); + break; + } + default: + MG_Util::Debug::LogE("Unsupported target: %s", MG_Util::Debug::GLEnumToString(target)); + } + } + MG_Util::Debug::LogD("Updated GLES texture %u (MobileGL ID)", mgTexId); + } + } + + void SyncAllBuffersToGLES(BufferState* bufferState) { + GLint prev_buf = 0; + // Delete removed buffers in GLES + std::vector buffersToErase; + for (auto it = s_bufferMap.begin(); it != s_bufferMap.end(); ++it) { + if (bufferState->buffers_.find(it->first) == bufferState->buffers_.end()) { + CallAndCheck(::GLES::glDeleteBuffers(1, &it->second);) + buffersToErase.push_back(it->first); + } + } + for (GLuint mgname : buffersToErase) { + s_bufferMap.erase(mgname); + } + CallAndCheck(::GLES::glGetIntegerv(GL_COPY_WRITE_BUFFER_BINDING, &prev_buf);) + + for (auto& [mgname, obj] : bufferState->buffers_) { + if (!obj.generated) + continue; + + if (!obj.dirty) + continue; + + // keep mapped buffer dirty, + // as it can change at any time + if (!obj.isMapped) + obj.dirty = false; + + GLuint glname = 0; + // Gen real buffers at ES + if (s_bufferMap.find(mgname) == s_bufferMap.end()) { + CallAndCheck(::GLES::glGenBuffers(1, &glname);) + s_bufferMap[mgname] = glname; + if constexpr (MG_Global::Common::LogLevel <= MG_Constants::Common::LOG_LEVEL_DEBUG) { + MG_Util::Debug::LogD("Creating buffer MG %d -> ES %d", mgname, glname); + std::string name = std::format("MG Buffer {}", mgname); + CallAndCheck(::GLES::glObjectLabel(GL_BUFFER, glname, name.length(), name.c_str());) + } + } else { + glname = s_bufferMap[mgname]; + } + + // Populate data to ES + CallAndCheck(::GLES::glBindBuffer(GL_COPY_WRITE_BUFFER, glname);) + // TODO: Check why obj.dataValid is broken for Minecraft 1.21.1- + MG_Util::Debug::LogD("bufferdata MG %d -> ES %d, size=%d, usage=%s", mgname, glname, obj.data.size(), MG_Util::Debug::GLEnumToString(obj.usage)); + CallAndCheck(::GLES::glBufferData( + GL_COPY_WRITE_BUFFER, + obj.data.size(), + obj.data.data(), //obj.dataValid || obj.isMapped ? obj.data.data() : nullptr, + obj.usage);) + + } + CallAndCheck(::GLES::glBindBuffer(GL_COPY_WRITE_BUFFER, prev_buf);) + } + + void GenCurrentVAONameToGLES(VertexArrayState* vaState) { + auto mgid = vaState->currentVao_; + auto& vao = vaState->vaos_[mgid]; + + if (!vao.generated) + return; + + GLuint glvao = 0; + if (s_vaoMap.find(mgid) == s_vaoMap.end()) { + CallAndCheck(::GLES::glGenVertexArrays(1, &glvao);) + s_vaoMap[mgid] = glvao; + MG_Util::Debug::LogD("Creating MG VAO: %d", mgid); + } else { + glvao = s_vaoMap[mgid]; + } + MG_Util::Debug::LogD("Updating MG VAO: %d", mgid); + CallAndCheck(::GLES::glBindVertexArray(glvao);) + } + + void SyncCurrentVAOToGLES(VertexArrayState* vaState) { + auto mgid = vaState->currentVao_; + auto& vao = vaState->vaos_[mgid]; + { + if (!vao.generated) + return; + + GLuint glvao = 0; + + if (vao.attribDirty) { + MG_Util::Debug::LogD("Updating MG VAO %d, dirty attrib", mgid); + // Update attrib + vao.attribDirty = false; + GLint prevArrayBuffer; + CallAndCheck(::GLES::glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer);) + for (auto& [index, attrib] : vao.attribs) { + GLuint buffer = attrib.buffer; + GLuint glBuffer = (buffer != 0) ? s_bufferMap[buffer] : 0; + CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, glBuffer);) + + MG_Util::Debug::LogD("attrib #%d: size=%d, type=%s, stride=%d, pointer=%d, %s, isInt=%s", + index, attrib.size, MG_Util::Debug::GLEnumToString(attrib.type), attrib.stride, attrib.pointer, + (attrib.enabled ? "enabled" : "disabled"), (attrib.isInteger ? "true" : "false")); + if (!attrib.isInteger) { + CallAndCheck(::GLES::glVertexAttribPointer( + index, attrib.size, attrib.type, + attrib.normalized ? GL_TRUE : GL_FALSE, + attrib.stride, attrib.pointer);) + } else { + CallAndCheck(::GLES::glVertexAttribIPointer( + index, attrib.size, attrib.type, + attrib.stride, attrib.pointer);) + } + + if (attrib.enabled) { + CallAndCheck(::GLES::glEnableVertexAttribArray(index);) + } else { + CallAndCheck(::GLES::glDisableVertexAttribArray(index);) } } - MG_Util::Debug::LogD("Updated existing GLES texture %u (MobileGL ID)", mgTexId); + CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer);) } - } - CallAndCheck(::GLES::glBindTexture(GL_TEXTURE_2D, 0);) - } - - static std::unordered_map s_bufferDirtyFlags_bufferObj; - void SyncAllBuffersToGLES(BufferState* bufferState) { - GLint currentVBO = 0, currentEBO = 0; - CallAndCheck(::GLES::glGetIntegerv(GL_ARRAY_BUFFER_BINDING, ¤tVBO);) - CallAndCheck(::GLES::glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, ¤tEBO);) - for (auto& [mgBufferId, bufferObj] : bufferState->buffers_) { - if (!bufferObj.generated) continue; - - // TODO: Check if the buffer changes rather than always update it. - if (s_bufferMap.find(mgBufferId) == s_bufferMap.end() || true) { - if (s_bufferMap.find(mgBufferId) == s_bufferMap.end()) { - GLuint glBuffer; - CallAndCheck(::GLES::glGenBuffers(1, &glBuffer);) - s_bufferMap[mgBufferId] = glBuffer; + if (vao.eboDirty) { + MG_Util::Debug::LogD("Updating MG VAO %d, dirty ebo", mgid); + // Update EBO + vao.eboDirty = false; + if (vao.elementBuffer != 0) { + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[vao.elementBuffer]);) + } else { + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);) } - CallAndCheck(::GLES::glBindBuffer(bufferObj.target, s_bufferMap[mgBufferId]);) - CallAndCheck(::GLES::glBufferData( - bufferObj.target, - bufferObj.data.size(), - bufferObj.data.data(), - bufferObj.usage - );) - s_bufferDirtyFlags_bufferObj[mgBufferId] = bufferObj.data.data(); } + } - CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, currentVBO);) - CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, currentEBO);) } + + void SyncAllVAOsToGLES(VertexArrayState* vaState) { + for (auto& [mgid, vao] : vaState->vaos_) { + if (!vao.generated) + continue; + + GLuint glvao = 0; + + if (vao.attribDirty || vao.eboDirty) { + if (s_vaoMap.find(mgid) == s_vaoMap.end()) { + CallAndCheck(::GLES::glGenVertexArrays(1, &glvao);) + s_vaoMap[mgid] = glvao; + MG_Util::Debug::LogD("Creating MG VAO: %d", mgid); + } else { + glvao = s_vaoMap[mgid]; + } + MG_Util::Debug::LogD("Updating MG VAO: %d", mgid); + CallAndCheck(::GLES::glBindVertexArray(glvao);) + } else continue; + + if (vao.attribDirty) { + MG_Util::Debug::LogD("Updating MG VAO %d, dirty attrib", mgid); + // Update attrib + vao.attribDirty = false; + for (auto& [index, attrib] : vao.attribs) { + MG_Util::Debug::LogD("attrib #%d: size=%d, type=%s, stride=%d, pointer=%d, %s, isInt=%s", + index, attrib.size, MG_Util::Debug::GLEnumToString(attrib.type), attrib.stride, attrib.pointer, + (attrib.enabled ? "enabled" : "disabled"), (attrib.isInteger ? "true" : "false")); + if (!attrib.isInteger) { + CallAndCheck(::GLES::glVertexAttribPointer( + index, attrib.size, attrib.type, + attrib.normalized ? GL_TRUE : GL_FALSE, + attrib.stride, attrib.pointer);) + } else { + CallAndCheck(::GLES::glVertexAttribIPointer( + index, attrib.size, attrib.type, + attrib.stride, attrib.pointer);) + } + + if (attrib.enabled) { + CallAndCheck(::GLES::glEnableVertexAttribArray(index);) + } else { + CallAndCheck(::GLES::glDisableVertexAttribArray(index);) + } + } + } + + if (vao.eboDirty) { + MG_Util::Debug::LogD("Updating MG VAO %d, dirty ebo", mgid); + // Update EBO + vao.eboDirty = false; + if (vao.elementBuffer != 0) { + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[vao.elementBuffer]);) + } else { + CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);) + } + } + + CallAndCheck(::GLES::glBindVertexArray(0);) + } + } + + static GLuint lastBoundProgram = 0; + static GLuint lastBoundFBO[2] = {0}; + static std::array lastBoundTextures; + + void RealizeFBOState(GLenum fbtype) { + FramebufferState* fbState = MG_State_T::framebufferState; + + GLuint fb = fbState->currentBindings_[fbtype]; + if (fb != lastBoundFBO[(fbtype == GL_DRAW_FRAMEBUFFER) ? 0 : 1]) { + GLuint glFBO = 0; + + if (fb == 0) { + CallAndCheck(::GLES::glBindFramebuffer(fbtype, 0);) + glFBO = 0; + } else { + bool isNewGlesFBO = false; + + if (s_framebufferMap.find(fb) == s_framebufferMap.end()) { + CallAndCheck(::GLES::glGenFramebuffers(1, &glFBO);) + s_framebufferMap[fb] = glFBO; + CallAndCheck(::GLES::glBindFramebuffer(fbtype, glFBO);) + MG_Util::Debug::LogD("Generated and bound new GLES FBO %u for MobileGL FBO %u", glFBO, fb); + isNewGlesFBO = true; + } else { + glFBO = s_framebufferMap[fb]; + CallAndCheck(::GLES::glBindFramebuffer(fbtype, glFBO);) + MG_Util::Debug::LogD("Bound existing GLES FBO %u for MobileGL FBO %u", glFBO, fb); + } + + if (glFBO != 0) { + FramebufferObject* mgFBO = fbState->GetCurrentFBO(fbtype); + if (mgFBO) { + MG_Util::Debug::LogD("Checking/Syncing attachments for GLES FBO %u (MobileGL FBO %u)", glFBO, fb); + + for (auto const& [mgAttachmentPoint, mgAtt] : mgFBO->attachments) { + + if (mgAtt.type != GL_TEXTURE_2D) { + MG_Util::Debug::LogW("Skipping non-TEXTURE_2D attachment 0x%X for FBO %u", mgAttachmentPoint, fb); + continue; + } + + GLuint expectedGLTexId = 0; + if (mgAtt.handle != 0) { + if (s_textureMap.find(mgAtt.handle) != s_textureMap.end()) { + expectedGLTexId = s_textureMap[mgAtt.handle]; + } else { + MG_Util::Debug::LogE("MobileGL Texture %u for FBO %u attachment %s not found in s_textureMap during FBO sync!", mgAtt.handle, fb, MG_Util::Debug::GLEnumToString(mgAttachmentPoint)); + for (auto const& [key, val] : s_textureMap) + MG_Util::Debug::LogW(" key: %d, val: %d", key, val); + continue; + } + } + + GLint glesAttachedType = 0; + GLint glesAttachedName = 0; + + CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(fbtype, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &glesAttachedType);) + + if (glesAttachedType == GL_TEXTURE) { + CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(fbtype, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &glesAttachedName);) + } else if (glesAttachedType != GL_NONE) { + MG_Util::Debug::LogW("GLES FBO %u attachment 0x%X has non-texture type 0x%X (expected GL_TEXTURE or GL_NONE)", glFBO, mgAttachmentPoint, glesAttachedType); + } + + if ((GLuint)glesAttachedName != expectedGLTexId) { + MG_Util::Debug::LogD("Syncing FBO %u attachment 0x%X: Expected GLES TexID %u, Found GLES ObjName %d. Attaching/Detaching...", + fb, mgAttachmentPoint, expectedGLTexId, glesAttachedName); + + CallAndCheck(::GLES::glFramebufferTexture2D( + fbtype, + mgAttachmentPoint, + GL_TEXTURE_2D, + expectedGLTexId, + mgAtt.mipLevel + );) + } + } + + GLenum status = ::GLES::glCheckFramebufferStatus(fbtype); + if (status != GL_FRAMEBUFFER_COMPLETE) { + MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after sync! Status: 0x%X (%s)", + fb, glFBO, status, MG_Util::Debug::GLEnumToString(status)); + } + + } else { + MG_Util::Debug::LogW("Could not get MobileGL FBO object for ID %u during attachment sync.", fb); + } + } + } + + lastBoundFBO[(fbtype == GL_DRAW_FRAMEBUFFER) ? 0 : 1] = fb; + } + } + + void DrawArraysSHITTILY(GLenum mode, GLint first, GLsizei count) { } - static GLuint lastBoundVAO = 0; - static GLuint lastBoundProgram = 0; - static GLuint lastBoundFBO = 0; - static std::array lastBoundTextures; void DrawElementsSHITTILY(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) { CommonState* commonState = MG_State_T::commonState; TextureState* textureState = MG_State_T::textureState; @@ -282,12 +810,21 @@ namespace MG_GL::GL { CallAndCheck(::GLES::glBindTexture(target, glTexId);) TextureObject& mgTex = textureState->textures[texId]; - + TexParamCache& entry = cache[&mgTex]; for (auto& [pname, param] : mgTex.params.texPropertiesInt) { - CallAndCheck(::GLES::glTexParameteri(target, pname, param);) + auto it = entry.lastInt.find(pname); + if (it == entry.lastInt.end() || it->second != param) { + CallAndCheck(::GLES::glTexParameteri(target, pname, param);) + entry.lastInt[pname] = param; + } } + for (auto& [pname, param] : mgTex.params.texPropertiesFloat) { - CallAndCheck(::GLES::glTexParameterf(target, pname, param);) + auto it = entry.lastFloat.find(pname); + if (it == entry.lastFloat.end() || it->second != param) { + CallAndCheck(::GLES::glTexParameterf(target, pname, param);) + entry.lastFloat[pname] = param; + } } for (auto& [level, mip] : mgTex.params.mipmapData) { @@ -308,92 +845,21 @@ namespace MG_GL::GL { SyncAllBuffersToGLES(bufferState); // VAO - GLuint mgVAO = vaState->currentVao_; - VertexArrayObject* vao = &vaState->vaos_[vaState->currentVao_]; - for (auto& [mgVAOId, mgVAO] : vaState->vaos_) { - if (!mgVAO.generated) continue; + GenCurrentVAONameToGLES(vaState); - // TODO: Check is the VAO changes rather than always update it. - //if (!s_vaoMap.count(mgVAOId)) { - GLuint glVAO; - if (!s_vaoMap.count(mgVAOId)) { - CallAndCheck(::GLES::glGenVertexArrays(1, &glVAO);) - s_vaoMap[mgVAOId] = glVAO; - - } else { - glVAO = s_vaoMap[mgVAOId]; - } - CallAndCheck(::GLES::glBindVertexArray(glVAO);) - - if (mgVAO.elementBuffer != 0 && s_bufferMap.count(mgVAO.elementBuffer)) { - CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[mgVAO.elementBuffer]);) - } - - for (auto& [index, attrib] : mgVAO.attribs) { - if (attrib.buffer != 0 && s_bufferMap.count(attrib.buffer)) { - CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, s_bufferMap[attrib.buffer]);) - - if (attrib.isInteger) { - CallAndCheck(::GLES::glVertexAttribIPointer( - index, attrib.size, attrib.type, - attrib.stride, attrib.pointer - );) - } else { - CallAndCheck(::GLES::glVertexAttribPointer( - index, attrib.size, attrib.type, - attrib.normalized ? GL_TRUE : GL_FALSE, - attrib.stride, attrib.pointer - );) - } - - if (attrib.enabled) { - CallAndCheck(::GLES::glEnableVertexAttribArray(index);) - } else { - CallAndCheck(::GLES::glDisableVertexAttribArray(index);) - } - } - } - CallAndCheck(::GLES::glBindVertexArray(0);) - //} - } GLuint currentMgVAO = vaState->currentVao_; - if (s_vaoMap.count(currentMgVAO)) { - CallAndCheck(::GLES::glBindVertexArray(s_vaoMap[currentMgVAO]);) + MG_Util::Debug::LogD("Now binding to VAO %d...", currentMgVAO); + CallAndCheck(::GLES::glBindVertexArray(s_vaoMap[currentMgVAO]);) - VertexArrayObject& currentVAO = vaState->vaos_[currentMgVAO]; - for (auto& [index, attrib] : currentVAO.attribs) { - if (attrib.enabled) { - CallAndCheck(::GLES::glEnableVertexAttribArray(index);) - } else { - CallAndCheck(::GLES::glDisableVertexAttribArray(index);) - } - } - } else { - CallAndCheck(::GLES::glBindVertexArray(0);) - } + GLuint vbo = bufferState->GetCurrentBinding(GL_ARRAY_BUFFER); + CallAndCheck(::GLES::glBindBuffer(GL_ARRAY_BUFFER, s_bufferMap[vbo]);) + MG_Util::Debug::LogD("binding vbo MG %d -> ES %d", vbo, s_bufferMap[vbo]); + SyncCurrentVAOToGLES(vaState); + // EBO - if (vao->elementBuffer != 0) { - if (s_bufferMap.count(vao->elementBuffer)) { - CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s_bufferMap[vao->elementBuffer]);) - } - } else if (indices != nullptr) { - static GLuint dynamicIBO = 0; - if (dynamicIBO == 0) { - CallAndCheck(::GLES::glGenBuffers(1, &dynamicIBO);) - } - - size_t typeSize = 0; - switch(type) { - case GL_UNSIGNED_BYTE: typeSize = sizeof(GLubyte); break; - case GL_UNSIGNED_SHORT: typeSize = sizeof(GLushort); break; - case GL_UNSIGNED_INT: typeSize = sizeof(GLuint); break; - } - size_t dataSize = count * typeSize; - - CallAndCheck(::GLES::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamicIBO);) - CallAndCheck(::GLES::glBufferData(GL_ELEMENT_ARRAY_BUFFER, dataSize, indices, GL_STREAM_DRAW);) - } + GLuint curVaoId = vaState->currentVao_; + VertexArrayObject* curvao = &vaState->vaos_[curVaoId]; // Program GLuint currentProgram = programState->GetCurrentProgram(); @@ -401,11 +867,19 @@ namespace MG_GL::GL { if (s_programMap.find(currentProgram) == s_programMap.end()) { GLuint glProgram = ::GLES::glCreateProgram(); ProgramObject& mgProgram = programState->programs_[currentProgram]; - + + // Attribute Names + // before vertex shader attach + for (auto& [name, idx]: mgProgram.attribLocations) { + MG_Util::Debug::LogD("%s: location = %d", name.c_str(), idx); + CallAndCheck(::GLES::glBindAttribLocation(glProgram, idx, name.c_str());) + } + // Shader for (GLuint shaderId : mgProgram.attachedShaders) { ShaderObject& mgShader = programState->shaders_[shaderId]; GLuint glShader = ::GLES::glCreateShader(mgShader.type); + std::string source = MG_Util::Program::CompileSPIRVToGLSL(mgShader.compiledSpirv, 320, true); // Post-processing ESSL source = removeLayoutBinding(source); @@ -578,98 +1052,9 @@ namespace MG_GL::GL { } // Framebuffer - GLuint currentFBO = fbState->currentBindings_[GL_DRAW_FRAMEBUFFER]; - if (currentFBO != lastBoundFBO) { - GLuint glFBO = 0; + RealizeFBOState(GL_DRAW_FRAMEBUFFER); - if (currentFBO == 0) { - CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, 0);) - glFBO = 0; - } else { - bool isNewGlesFBO = false; - - if (s_framebufferMap.find(currentFBO) == s_framebufferMap.end()) { - CallAndCheck(::GLES::glGenFramebuffers(1, &glFBO);) - s_framebufferMap[currentFBO] = glFBO; - CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) - MG_Util::Debug::LogD("Generated and bound new GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); - GLenum status = ::GLES::glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after creation! Status: 0x%X (%s)", - currentFBO, glFBO, status, MG_Util::Debug::GLEnumToString(status)); - } - isNewGlesFBO = true; - } else { - glFBO = s_framebufferMap[currentFBO]; - CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) - MG_Util::Debug::LogD("Bound existing GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); - } - - if (glFBO != 0) { - FramebufferObject* mgFBO = fbState->GetCurrentFBO(GL_DRAW_FRAMEBUFFER); - if (mgFBO) { - MG_Util::Debug::LogD("Checking/Syncing attachments for GLES FBO %u (MobileGL FBO %u)", glFBO, currentFBO); - - for (auto const& [mgAttachmentPoint, mgAtt] : mgFBO->attachments) { - - if (mgAtt.type != GL_TEXTURE_2D) { - MG_Util::Debug::LogW("Skipping non-TEXTURE_2D attachment 0x%X for FBO %u", mgAttachmentPoint, currentFBO); - continue; - } - - GLuint expectedGLTexId = 0; - if (mgAtt.handle != 0) { - if (s_textureMap.count(mgAtt.handle)) { - expectedGLTexId = s_textureMap[mgAtt.handle]; - } else { - MG_Util::Debug::LogE("MobileGL Texture %u for FBO %u attachment 0x%X not found in s_textureMap during FBO sync!", mgAtt.handle, currentFBO, mgAttachmentPoint); - for (auto const& [key, val] : s_textureMap) - MG_Util::Debug::LogW(" key: %d, val: %d", key, val); - continue; - } - } - - GLint glesAttachedType = 0; - GLint glesAttachedName = 0; - - CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &glesAttachedType);) - - if (glesAttachedType == GL_TEXTURE) { - CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &glesAttachedName);) - } else if (glesAttachedType != GL_NONE) { - MG_Util::Debug::LogW("GLES FBO %u attachment 0x%X has non-texture type 0x%X (expected GL_TEXTURE or GL_NONE)", glFBO, mgAttachmentPoint, glesAttachedType); - } - - if ((GLuint)glesAttachedName != expectedGLTexId) { - MG_Util::Debug::LogD("Syncing FBO %u attachment 0x%X: Expected GLES TexID %u, Found GLES ObjName %d. Attaching/Detaching...", - currentFBO, mgAttachmentPoint, expectedGLTexId, glesAttachedName); - - CallAndCheck(::GLES::glFramebufferTexture2D( - GL_FRAMEBUFFER, - mgAttachmentPoint, - GL_TEXTURE_2D, - expectedGLTexId, - mgAtt.mipLevel - );) - } - } - GLenum status = ::GLES::glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after sync! Status: 0x%X (%s)", - currentFBO, glFBO, status, MG_Util::Debug::GLEnumToString(status)); - } - - } else { - MG_Util::Debug::LogW("Could not get MobileGL FBO object for ID %u during attachment sync.", currentFBO); - } - } - } - - lastBoundFBO = currentFBO; - } - - - if (vao->elementBuffer != 0 || indices != nullptr) { + if (curvao->elementBuffer != 0 || indices != nullptr) { CallAndCheck(::GLES::glDrawElements( mode, count, @@ -679,96 +1064,48 @@ namespace MG_GL::GL { } } + void BlitFramebuffer(GLint srcX0, + GLint srcY0, + GLint srcX1, + GLint srcY1, + GLint dstX0, + GLint dstY0, + GLint dstX1, + GLint dstY1, + GLbitfield mask, + GLenum filter) { + MG_Util::Debug::LogD("BlitFramebuffer, srcX0=%d, srcY0=%d, srcX1=%d, srcY1=%d, dstX0=%d, dstY0=%d, dstX1=%d, dstY1=%d, mask=0x%x, filter=%s", + srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, MG_Util::Debug::GLEnumToString(filter)); + TextureState* textureState = MG_State_T::textureState; + + // Texture + SyncAllTexturesToGLES(textureState); + + // Realize FBO states + RealizeFBOState(GL_DRAW_FRAMEBUFFER); + RealizeFBOState(GL_READ_FRAMEBUFFER); + ::GLES::glBlitFramebuffer(srcX0, + srcY0, + srcX1, + srcY1, + dstX0, + dstY0, + dstX1, + dstY1, + mask, + filter); + } + void ClearSHITTILY(GLbitfield mask) { CommonState* commonState = MG_State_T::commonState; FramebufferState* fbState = MG_State_T::framebufferState; - GLuint currentFBO = fbState->currentBindings_[GL_DRAW_FRAMEBUFFER]; - if (currentFBO != lastBoundFBO) { - GLuint glFBO = 0; + TextureState* textureState = MG_State_T::textureState; - if (currentFBO == 0) { - CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, 0);) - glFBO = 0; - } else { - bool isNewGlesFBO = false; - - if (s_framebufferMap.find(currentFBO) == s_framebufferMap.end()) { - CallAndCheck(::GLES::glGenFramebuffers(1, &glFBO);) - s_framebufferMap[currentFBO] = glFBO; - CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) - MG_Util::Debug::LogD("Generated and bound new GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); - isNewGlesFBO = true; - } else { - glFBO = s_framebufferMap[currentFBO]; - CallAndCheck(::GLES::glBindFramebuffer(GL_FRAMEBUFFER, glFBO);) - MG_Util::Debug::LogD("Bound existing GLES FBO %u for MobileGL FBO %u", glFBO, currentFBO); - } - - if (glFBO != 0) { - FramebufferObject* mgFBO = fbState->GetCurrentFBO(GL_DRAW_FRAMEBUFFER); - if (mgFBO) { - MG_Util::Debug::LogD("Checking/Syncing attachments for GLES FBO %u (MobileGL FBO %u)", glFBO, currentFBO); - - for (auto const& [mgAttachmentPoint, mgAtt] : mgFBO->attachments) { - - if (mgAtt.type != GL_TEXTURE_2D) { - MG_Util::Debug::LogW("Skipping non-TEXTURE_2D attachment 0x%X for FBO %u", mgAttachmentPoint, currentFBO); - continue; - } - - GLuint expectedGLTexId = 0; - if (mgAtt.handle != 0) { - if (s_textureMap.count(mgAtt.handle)) { - expectedGLTexId = s_textureMap[mgAtt.handle]; - } else { - MG_Util::Debug::LogE("MobileGL Texture %u for FBO %u attachment 0x%X not found in s_textureMap during FBO sync!", mgAtt.handle, currentFBO, mgAttachmentPoint); - for (auto const& [key, val] : s_textureMap) - MG_Util::Debug::LogW(" key: %d, val: %d", key, val); - continue; - } - } - - GLint glesAttachedType = 0; - GLint glesAttachedName = 0; - - CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &glesAttachedType);) - - if (glesAttachedType == GL_TEXTURE) { - CallAndCheck(::GLES::glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, mgAttachmentPoint, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &glesAttachedName);) - } else if (glesAttachedType != GL_NONE) { - MG_Util::Debug::LogW("GLES FBO %u attachment 0x%X has non-texture type 0x%X (expected GL_TEXTURE or GL_NONE)", glFBO, mgAttachmentPoint, glesAttachedType); - } - - if ((GLuint)glesAttachedName != expectedGLTexId) { - MG_Util::Debug::LogD("Syncing FBO %u attachment 0x%X: Expected GLES TexID %u, Found GLES ObjName %d. Attaching/Detaching...", - currentFBO, mgAttachmentPoint, expectedGLTexId, glesAttachedName); - - CallAndCheck(::GLES::glFramebufferTexture2D( - GL_FRAMEBUFFER, - mgAttachmentPoint, - GL_TEXTURE_2D, - expectedGLTexId, - mgAtt.mipLevel - );) - } - } - - GLenum status = ::GLES::glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - MG_Util::Debug::LogE("Framebuffer %u (GLES FBO %u) is not complete after sync! Status: 0x%X (%s)", - currentFBO, glFBO, status, MG_Util::Debug::GLEnumToString(status)); - } - - } else { - MG_Util::Debug::LogW("Could not get MobileGL FBO object for ID %u during attachment sync.", currentFBO); - } - } - } - - lastBoundFBO = currentFBO; - } + // Texture + SyncAllTexturesToGLES(textureState); + RealizeFBOState(GL_DRAW_FRAMEBUFFER); static GLfloat lastClearColor[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; if (memcmp(lastClearColor, commonState->clearColor, sizeof(lastClearColor)) != 0) { diff --git a/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.cpp b/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.cpp index 7a4945bc..562c03cc 100644 --- a/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.cpp +++ b/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.cpp @@ -45,6 +45,7 @@ namespace MG_GL::GL { GLenum result = MG_State::AttachTexture2DToFramebuffer( target, attachment, textarget, texture, level ); + if (result == GL_NO_ERROR) return; MG_State::SetError(result); MG_Util::Debug::LogE("Texture attachment failed: %s", MG_Util::Debug::GLEnumToString(result)); diff --git a/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.h b/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.h index 712b3289..1ecf9a43 100644 --- a/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.h +++ b/MG/MG_GL/Implementations/GL/Framebuffer/GL_Framebuffer.h @@ -13,6 +13,7 @@ namespace MG_GL::GL { void BindFramebuffer(GLenum target, GLuint framebuffer); void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLenum CheckFramebufferStatus(GLenum target); + void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); } #endif //MOBILEGL_GL_FRAMEBUFFER_H diff --git a/MG/MG_GL/Implementations/GL/GLFuncsDefinitions/GLFuncsDefinitions.cpp b/MG/MG_GL/Implementations/GL/GLFuncsDefinitions/GLFuncsDefinitions.cpp index 8c0af8fb..1b7f4acd 100644 --- a/MG/MG_GL/Implementations/GL/GLFuncsDefinitions/GLFuncsDefinitions.cpp +++ b/MG/MG_GL/Implementations/GL/GLFuncsDefinitions/GLFuncsDefinitions.cpp @@ -61,7 +61,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyTexSubImage2D, GLenum target, GLint leve DECLARE_GL_FUNCTION_HEAD(GLuint, CreateProgram) DECLARE_GL_FUNCTION_END(GLuint, CreateProgram) DECLARE_GL_FUNCTION_HEAD(GLuint, CreateShader, GLenum type) DECLARE_GL_FUNCTION_END(GLuint, CreateShader, type) DECLARE_GL_FUNCTION_STUB_HEAD(void, CullFace, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CullFace, mode) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteBuffers, GLsizei n, const GLuint *buffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteBuffers, n,buffers) +DECLARE_GL_FUNCTION_HEAD(void, DeleteBuffers, GLsizei n, const GLuint *buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteBuffers, n,buffers) DECLARE_GL_FUNCTION_HEAD(void, DeleteFramebuffers, GLsizei n, const GLuint *framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteFramebuffers, n,framebuffers) DECLARE_GL_FUNCTION_HEAD(void, DeleteProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteProgram, program) DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteRenderbuffers, GLsizei n, const GLuint *renderbuffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteRenderbuffers, n,renderbuffers) @@ -197,7 +197,8 @@ DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x4fv, GLint location, GLsizei count DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x2fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x2fv, location,count,transpose,value) DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x4fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x4fv, location,count,transpose,value) DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x3fv, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x3fv, location,count,transpose,value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlitFramebuffer, srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter) +DECLARE_GL_FUNCTION_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlitFramebuffer, srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter) +//DECLARE_GL_FUNCTION_STUB_HEAD(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlitFramebuffer, srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter) DECLARE_GL_FUNCTION_STUB_HEAD(void, RenderbufferStorageMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, RenderbufferStorageMultisample, target,samples,internalformat,width,height) DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferTextureLayer, GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferTextureLayer, target,attachment,texture,level,layer) DECLARE_GL_FUNCTION_STUB_HEAD(void, FlushMappedBufferRange, GLenum target, GLintptr offset, GLsizeiptr length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FlushMappedBufferRange, target,offset,length) diff --git a/MG/MG_GL/Implementations/GL/VertexArray/GL_VertexArray.cpp b/MG/MG_GL/Implementations/GL/VertexArray/GL_VertexArray.cpp index 04918d3b..94b8d0b8 100644 --- a/MG/MG_GL/Implementations/GL/VertexArray/GL_VertexArray.cpp +++ b/MG/MG_GL/Implementations/GL/VertexArray/GL_VertexArray.cpp @@ -7,7 +7,7 @@ namespace MG_GL::GL { void GenVertexArrays(GLsizei n, GLuint* arrays) { MG_Util::Debug::LogD("glGenVertexArrays, n: %d, arrays: %p", n, arrays); - GLenum result = MG_State::CreateVertexArrays(n, arrays); + GLenum result = MG_State::GenVertexArraysNames(n, arrays); if (result == GL_NO_ERROR) return; MG_State::SetError(result); MG_Util::Debug::LogE("Error from MG State: %s", MG_Util::Debug::GLEnumToString(result)); diff --git a/MG/MG_GL/State/Buffer/BufferState.cpp b/MG/MG_GL/State/Buffer/BufferState.cpp index 4dde0d53..111af394 100644 --- a/MG/MG_GL/State/Buffer/BufferState.cpp +++ b/MG/MG_GL/State/Buffer/BufferState.cpp @@ -6,57 +6,80 @@ #include "BufferState.h" -GLenum BufferState::Create(GLuint* buffer) { - MG_Util::Debug::LogD("MG_State: Buffer: Create called"); - if (!buffer) return GL_INVALID_VALUE; +GLenum BufferState::GenName(GLuint *buffer) { + MG_Util::Debug::LogD("MG_State: Buffer: GenName"); + if (!buffer) + return GL_INVALID_VALUE; GLuint id = 0; - if (!freeIds_.empty()) { - id = *freeIds_.begin(); - freeIds_.erase(freeIds_.begin()); + if (freeId_.empty()) { + id = lastId_++; } else { - id = ++lastId_; + id = freeId_.back(); + freeId_.pop_back(); } + *buffer = id; - BufferObject& obj = buffers_[id]; - MG_Util::Debug::LogD("MG_State: Buffer: Create created buffer %d", id); - obj.generated = true; + MG_Util::Debug::LogD("MG_State: Buffer: Gen new name %d", id); return GL_NO_ERROR; } -GLenum BufferState::CreateN(GLsizei n, GLuint* buffers) { - MG_Util::Debug::LogD("MG_State: Buffer: CreateN called with n=%d", n); +GLenum BufferState::GenNameN(GLsizei n, GLuint* buffers) { + MG_Util::Debug::LogD("MG_State: Buffer: GenNameN called with n=%d", n); if (n < 0) return GL_INVALID_VALUE; for (GLsizei i = 0; i < n; ++i) { - GLenum result = Create(&buffers[i]); + GLenum result = GenName(&buffers[i]); if (result != GL_NO_ERROR) { - MG_Util::Debug::LogE("MG_State: Buffer: CreateN create buffer failed with error 0x%x", result); + MG_Util::Debug::LogE("MG_State: Buffer: GenNameN failed with error 0x%x", result); return result; } } - MG_Util::Debug::LogD("MG_State: Buffer: CreateN created buffers successfully"); + MG_Util::Debug::LogD("MG_State: Buffer: GenNameN created buffers successfully"); return GL_NO_ERROR; } -GLenum BufferState::Bind(GLenum target, GLuint buffer) { - if (!IsValidTarget_(target)) return GL_INVALID_ENUM; - MG_Util::Debug::LogD("MG_State: Buffer: Bind called with target=0x%x, buffer=%u", target, buffer); +GLenum BufferState::Create(GLuint buffer) { + MG_Util::Debug::LogD("MG_State: Buffer: Create called"); + if (!buffer) + return GL_INVALID_VALUE; - if (buffer != 0) { - auto it = buffers_.find(buffer); - if (it == buffers_.end()) { - buffers_[buffer]; - } else { - BufferObject& obj = it->second; - if (obj.target != 0 && obj.target != target) { - MG_Util::Debug::LogE("MG_State: Buffer: Bind can not bind a buffer to different target 0x%x, current bind target is 0x%x", target, obj.target); - return GL_INVALID_OPERATION; - } - } - buffers_[buffer].target = target; - } + if (ValidateAllocatedHandle(buffer)) + return GL_INVALID_VALUE; + + BufferObject& obj = buffers_[buffer]; + MG_Util::Debug::LogD("MG_State: Buffer: Create created buffer %d", buffer); + obj.generated = true; + obj.dirty = true; + + return GL_NO_ERROR; +} + +//GLenum BufferState::CreateN(GLsizei n, GLuint* buffers) { +// MG_Util::Debug::LogD("MG_State: Buffer: CreateN called with n=%d", n); +// if (n < 0) return GL_INVALID_VALUE; +// +// for (GLsizei i = 0; i < n; ++i) { +// GLenum result = Create(&buffers[i]); +// if (result != GL_NO_ERROR) { +// MG_Util::Debug::LogE("MG_State: Buffer: CreateN create buffer failed with error 0x%x", result); +// return result; +// } +// } +// MG_Util::Debug::LogD("MG_State: Buffer: CreateN created buffers successfully"); +// return GL_NO_ERROR; +//} + +GLenum BufferState::Bind(GLenum target, GLuint buffer) { + // We don't handle unallocated buffer names here, just plain bind + if (!IsValidTarget_(target)) return GL_INVALID_ENUM; + MG_Util::Debug::LogD("MG_State: Buffer: Bind called with target=%s, buffer=%u", MG_Util::Debug::GLEnumToString(target), buffer); + +// if (buffer != 0 && !ValidateAllocatedHandle(buffer)) { +// MG_Util::Debug::LogE("MG_State: Buffer: Binding invalid buffer %d to %s", buffer, MG_Util::Debug::GLEnumToString(target)); +// return GL_INVALID_OPERATION; +// } currentBindings_[target] = buffer; MG_Util::Debug::LogD("MG_State: Buffer: Bind succeed bind buffer %u to target 0x%x", buffer, target); @@ -73,7 +96,11 @@ GLenum BufferState::CommitStorage(GLenum target, GLsizeiptr size, const void* da MG_Util::Debug::LogD("MG_State: Buffer: CommitStorage get buffer object %u at target 0x%x",it->second,target); obj.usage = usage; obj.data.resize(size); - if (data) memcpy(obj.data.data(), data, size); + if (data) { + memcpy(obj.data.data(), data, size); + obj.dataValid = true; + } + obj.dirty = true; MG_Util::Debug::LogD("MG_State: Buffer: CommitStorage buffer at target 0x%x committed storage, size = %zu, usage=0x%x", target, size, usage); return GL_NO_ERROR; @@ -95,8 +122,8 @@ GLenum BufferState::AcquireBufferMemory(GLenum target, GLenum access, void** map obj.isMapped = true; *mappedPointer = obj.data.data(); - obj.isMapped = true; obj.accessMode = access; + obj.dirty = true; return GL_NO_ERROR; } @@ -115,26 +142,45 @@ GLenum BufferState::ReleaseBufferMemory(GLenum target) { obj.isMapped = false; obj.accessMode = GL_READ_WRITE; + obj.dirty = true; MG_Util::Debug::LogD("MG_State: Buffer: ReleaseBufferMemory buffer at target 0x%x released mapped memory", target); return GL_NO_ERROR; } -bool BufferState::ValidateHandle(GLuint buffer) { - bool isvalid = buffers_.count(buffer) > 0; - MG_Util::Debug::LogD("MG_State: Buffer: ValidateHandle called on buffer %u returns %d", buffer, isvalid); - return isvalid; +bool BufferState::ValidateAllocatedHandle(GLuint buffer) { + bool isValid = buffers_.find(buffer) != buffers_.end(); + MG_Util::Debug::LogD("MG_State: Buffer: ValidateAllocatedHandle called on buffer %u returns %d", buffer, isValid); + return isValid; +} + +bool BufferState::ValidateGeneratedName(GLuint buffer) { + bool inFreeList = std::find(freeId_.begin(), freeId_.end(), buffer) != freeId_.end(); + bool lessThanLast = buffer < lastId_; // lastId_ is not generated yet + MG_Util::Debug::LogD("MG_State: Buffer: ValidateGeneratedName called on buffer %u returns %d", buffer, lessThanLast && !inFreeList); + return lessThanLast && !inFreeList; } void BufferState::Delete(GLuint buffer) { - if (buffers_.erase(buffer)) { - freeIds_.insert(buffer); - for (auto& [target, id] : currentBindings_) { - if (id == buffer) id = 0; - } + buffers_.erase(buffer); + if (ValidateGeneratedName(buffer)) + freeId_.emplace_back(buffer); + for (auto& [target, id] : currentBindings_) { + if (id == buffer) id = 0; } MG_Util::Debug::LogD("MG_State: Buffer: Delete buffer %u", buffer); } +GLenum BufferState::DeleteN(GLsizei n, const GLuint* buffers) { + MG_Util::Debug::LogD("MG_State: Buffer: DeleteN called with n=%d", n); + if (n < 0) return GL_INVALID_VALUE; + + for (GLsizei i = 0; i < n; ++i) { + Delete(buffers[i]); + } + MG_Util::Debug::LogD("MG_State: Buffer: DeleteN deleted buffers successfully"); + return GL_NO_ERROR; +} + bool BufferState::IsValidTarget_(GLenum target) { MG_Util::Debug::LogD("MG_State: Buffer: IsValidTarget_ called with target=0x%x,result=%d", target, !(MG_Constants::Buffer::VALID_TARGETS.find(target) == MG_Constants::Buffer::VALID_TARGETS.end())); return !(MG_Constants::Buffer::VALID_TARGETS.find(target) == MG_Constants::Buffer::VALID_TARGETS.end()); @@ -179,7 +225,7 @@ GLenum BufferState::QueryPropertyIntVector(GLenum target, GLenum pname, GLint* p case GL_BUFFER_USAGE: *params = static_cast(buffer.usage); break; - MG_Util::Debug::LogD("MG_State: Buffer: QueryPropertyIntVector Query info about buffer %u succeed",buffer.target); + MG_Util::Debug::LogD("MG_State: Buffer: QueryPropertyIntVector Query info about buffer %u succeed", target); default: return GL_INVALID_ENUM; } diff --git a/MG/MG_GL/State/Buffer/BufferState.h b/MG/MG_GL/State/Buffer/BufferState.h index 948953f3..df21623e 100644 --- a/MG/MG_GL/State/Buffer/BufferState.h +++ b/MG/MG_GL/State/Buffer/BufferState.h @@ -9,34 +9,42 @@ #include "../../../Includes.h" class BufferState { + template + using unordered_map = ankerl::unordered_dense::map; + public: struct BufferObject { - GLenum target = 0; GLenum usage = GL_STATIC_DRAW; std::vector data; + bool dataValid = false; + bool dirty = false; // TODO: encapsulate this with an public API to RHI bool isMapped = false; bool generated = false; GLenum accessMode = GL_READ_WRITE; }; // Return: the validity of the operation, according to OpenGL 3 standard - GLenum Create(GLuint* buffer); - GLenum CreateN(GLsizei n, GLuint* buffers); + GLenum GenName(GLuint* buffer); + GLenum GenNameN(GLsizei n, GLuint* buffers); + GLenum Create(GLuint buffer); +// GLenum CreateN(GLsizei n, GLuint* buffers); GLenum Bind(GLenum target, GLuint buffer); GLenum CommitStorage(GLenum target, GLsizeiptr size, const void* data, GLenum usage); GLenum AcquireBufferMemory(GLenum target, GLenum access, void** mappedPointer); GLenum ReleaseBufferMemory(GLenum target); GLenum QueryPropertyIntVector(GLenum target, GLenum pname, GLint* params) const; - bool ValidateHandle(GLuint buffer); + bool ValidateAllocatedHandle(GLuint buffer); + bool ValidateGeneratedName(GLuint buffer); void Delete(GLuint buffer); + GLenum DeleteN(GLsizei n, const GLuint* buffers); GLuint GetCurrentBinding(GLenum target) const; - std::unordered_map currentBindings_; - std::unordered_map buffers_; + unordered_map currentBindings_; + unordered_map buffers_; private: - std::set freeIds_; - GLuint lastId_ = 0; + std::vector freeId_; + GLuint lastId_ = 1; static bool IsValidTarget_(GLenum target); }; diff --git a/MG/MG_GL/State/Common/CommonState.h b/MG/MG_GL/State/Common/CommonState.h index b19417bb..0feccbe3 100644 --- a/MG/MG_GL/State/Common/CommonState.h +++ b/MG/MG_GL/State/Common/CommonState.h @@ -9,6 +9,8 @@ #include "../../../Includes.h" class CommonState { + template + using unordered_map = ankerl::unordered_dense::map; public: // Viewport GLint viewport[4] = {0, 0, 0, 0}; @@ -17,7 +19,7 @@ public: GLboolean colorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE}; // Pixel storage parameters - std::unordered_map pixelStoreParams; + unordered_map pixelStoreParams; // Blend state GLenum blendSrcRGB = GL_ONE; @@ -35,7 +37,7 @@ public: GLboolean depthMask = GL_TRUE; // Capability enables - std::unordered_map capabilities; + unordered_map capabilities; CommonState(); diff --git a/MG/MG_GL/State/Core/GLState.cpp b/MG/MG_GL/State/Core/GLState.cpp index f7f5f290..ab8fe3fd 100644 --- a/MG/MG_GL/State/Core/GLState.cpp +++ b/MG/MG_GL/State/Core/GLState.cpp @@ -160,12 +160,16 @@ namespace MG_State { return MG_State_T::bufferState->ReleaseBufferMemory(target); } - GLenum CreateBuffer(GLuint* buffer) { + GLenum CreateBuffer(GLuint buffer) { return MG_State_T::bufferState->Create(buffer); } - GLenum CreateBuffers(GLsizei n, GLuint* buffers) { - return MG_State_T::bufferState->CreateN(n, buffers); +// GLenum CreateBuffers(GLsizei n, GLuint* buffers) { +// return MG_State_T::bufferState->CreateN(n, buffers); +// } + + GLenum GenBufferNames(GLsizei n, GLuint* buffers) { + return MG_State_T::bufferState->GenNameN(n, buffers); } GLenum BindBuffer(GLenum target, GLuint buffer) { @@ -173,6 +177,7 @@ namespace MG_State { auto* vao = MG_State_T::vertexArrayState->GetCurrentVAO(); if (!vao) return GL_INVALID_OPERATION; vao->elementBuffer = (GLuint)buffer; + vao->eboDirty = true; } return MG_State_T::bufferState->Bind(target, buffer); } @@ -181,14 +186,22 @@ namespace MG_State { return MG_State_T::bufferState->CommitStorage(target, size, data, usage); } - bool ValidateBufferHandle(GLuint buffer) { - return MG_State_T::bufferState->ValidateHandle(buffer); + bool ValidateAllocatedBufferHandle(GLuint buffer) { + return MG_State_T::bufferState->ValidateAllocatedHandle(buffer); + } + + bool ValidateGeneratedName(GLuint buffer) { + return MG_State_T::bufferState->ValidateGeneratedName(buffer); } void DeleteBuffer(GLuint buffer) { return MG_State_T::bufferState->Delete(buffer); } + GLenum DeleteBuffers(GLsizei n, const GLuint* buffers) { + return MG_State_T::bufferState->DeleteN(n, buffers); + } + GLenum QueryBufferPropertyIntVector(GLenum target, GLenum pname, GLint* params) { return MG_State_T::bufferState->QueryPropertyIntVector(target, pname, params); } @@ -206,6 +219,10 @@ namespace MG_State { return MG_State_T::vertexArrayState->CreateN(n, arrays); } + GLenum GenVertexArraysNames(GLsizei n, GLuint* arrays) { + return MG_State_T::vertexArrayState->GenNameN(n, arrays); + } + GLenum EnableVertexAttribArray(GLuint index) { return MG_State_T::vertexArrayState->EnableAttrib(index); } diff --git a/MG/MG_GL/State/Core/GLState.h b/MG/MG_GL/State/Core/GLState.h index e86b040b..1845aa6e 100644 --- a/MG/MG_GL/State/Core/GLState.h +++ b/MG/MG_GL/State/Core/GLState.h @@ -58,17 +58,21 @@ namespace MG_State { // Buffer GLenum AcquireBufferMemory(GLenum target, GLenum access, void** mappedPtr); GLenum ReleaseBufferMemory(GLenum target); - GLenum CreateBuffer(GLuint* buffer); - GLenum CreateBuffers(GLsizei n, GLuint* buffers); + GLenum CreateBuffer(GLuint buffer); +// GLenum CreateBuffers(GLsizei n, GLuint* buffers); + GLenum GenBufferNames(GLsizei n, GLuint* buffers); GLenum BindBuffer(GLenum target, GLuint buffer); GLenum CommitBufferStorage(GLenum target, GLsizeiptr size, const void* data, GLenum usage); - bool ValidateBufferHandle(GLuint buffer); + bool ValidateAllocatedBufferHandle(GLuint buffer); + bool ValidateGeneratedName(GLuint buffer); void DeleteBuffer(GLuint buffer); + GLenum DeleteBuffers(GLsizei n, const GLuint* buffers); GLenum QueryBufferPropertyIntVector(GLenum target, GLenum pname, GLint* params); // VertexArray GLenum CreateVertexArray(GLuint* array); GLenum CreateVertexArrays(GLsizei n, GLuint* arrays); + GLenum GenVertexArraysNames(GLsizei n, GLuint* arrays); GLenum BindVertexArray(GLuint array); GLenum EnableVertexAttribArray(GLuint index); GLenum DisableVertexAttribArray(GLuint index); diff --git a/MG/MG_GL/State/Texture/TextureState.cpp b/MG/MG_GL/State/Texture/TextureState.cpp index 3a9c70ab..78f552f3 100644 --- a/MG/MG_GL/State/Texture/TextureState.cpp +++ b/MG/MG_GL/State/Texture/TextureState.cpp @@ -11,7 +11,7 @@ // TextureObject bool TextureObject::IsImmutable() const { - bool immutable = params.texPropertiesInt.count(GL_TEXTURE_IMMUTABLE_FORMAT); + bool immutable = params.texPropertiesInt.find(GL_TEXTURE_IMMUTABLE_FORMAT) != params.texPropertiesInt.end(); MG_Util::Debug::LogD("MG_State: Texture: TextureObject::IsImmutable returns %d", immutable); return immutable; } @@ -54,7 +54,7 @@ bool TextureState::IsTextureGenerated(GLuint texture) { if (!isValidTexture) { MG_Util::Debug::LogD("MG_State: Texture: IsTextureGenerated invalid texture %d", texture); } else { - generated = textures.at(texture).generated; + generated = textures[texture].generated; if (!generated) { MG_Util::Debug::LogD("MG_State: Texture: IsTextureGenerated texture %d not generated", texture, generated); } @@ -101,12 +101,12 @@ GLenum TextureState::CreateN(GLsizei n, GLuint* textures) { for (GLsizei i = 0; i < n; ++i) { GLuint id = 0; - if (!freeIDs_.empty()) { - id = *freeIDs_.begin(); - freeIDs_.erase(freeIDs_.begin()); + if (!freeID_.empty()) { + id = freeID_.back(); + freeID_.pop_back(); MG_Util::Debug::LogD("MG_State: Texture: CreateN reusing free id=%u", id); } else { - id = ++lastUsedID_; + id = lastUsedID_++; MG_Util::Debug::LogD("MG_State: Texture: CreateN new id=%u", id); } TextureObject obj; @@ -258,7 +258,7 @@ GLenum TextureState::Upload2D(GLenum target, GLint level, GLint internalFormat, if (isProxyTexture) { MG_Util::Debug::LogD("MG_State: Texture: Upload2D proxy texture detected"); TextureObject& proxyTex = proxyTextures_[target]; - TextureParams::MipmapLevel mip{}; + auto& mip = proxyTex.params.mipmapData[level]; mip.width = width; mip.height = height; mip.internalFormat = internalFormat; @@ -266,19 +266,25 @@ GLenum TextureState::Upload2D(GLenum target, GLint level, GLint internalFormat, mip.type = type; proxyTex.generated = true; proxyTex.target = target; - proxyTex.params.mipmapData[level] = mip; return GL_NO_ERROR; } GLuint boundTex = textureUnits_[activeTextureUnit_].GetBoundTexture(target); TextureObject& tex = textures[boundTex]; - TextureParams::MipmapLevel mip{}; +// TextureParams::MipmapLevel mip{}; + + auto& mip = tex.params.mipmapData[level]; mip.width = width; mip.height = height; mip.internalFormat = internalFormat; mip.format = format; mip.type = type; - if (data != nullptr) { + mip.dirty = true; + + if (data == nullptr) { + mip.hasData = false; + MG_Util::Debug::LogD("MG_State: Texture: Upload2D data pointer is null"); + } else { GLint unpackSwapBytes = GetUnpackParam_(GL_UNPACK_SWAP_BYTES); GLint unpackLSBFirst = GetUnpackParam_(GL_UNPACK_LSB_FIRST); GLint unpackSkipPixels = GetUnpackParam_(GL_UNPACK_SKIP_PIXELS); @@ -288,47 +294,69 @@ GLenum TextureState::Upload2D(GLenum target, GLint level, GLint internalFormat, GLint unpackImageHeight = GetUnpackParam_(GL_UNPACK_IMAGE_HEIGHT); GLint unpackSkipImages = GetUnpackParam_(GL_UNPACK_SKIP_IMAGES); - GLsizei rowLength = (unpackRowLength > 0) ? unpackRowLength : width; + bool isDefaultUnpack = (unpackSwapBytes == 0) && (unpackLSBFirst == 0) && + (unpackSkipPixels == 0) && (unpackSkipRows == 0) && + (unpackRowLength == 0) && (unpackAlignment == 4) && + (unpackImageHeight == 0) && (unpackSkipImages == 0); + size_t bytesPerPixel = CalculateBytesPerPixel_(format, type); size_t componentSize = GetComponentSize_(type); - size_t srcRowSize = rowLength * bytesPerPixel; - size_t srcRowStride = (srcRowSize + unpackAlignment - 1) & ~(unpackAlignment - 1); - size_t srcImageStride = (unpackImageHeight > 0) ? - srcRowStride * unpackImageHeight : - srcRowStride * height; - - const GLubyte* srcData = static_cast(data); - srcData += unpackSkipImages * srcImageStride; - srcData += unpackSkipRows * srcRowStride; - srcData += unpackSkipPixels * bytesPerPixel; - size_t dstRowStride = width * bytesPerPixel; size_t dstSize = dstRowStride * height; mip.pixelData.resize(dstSize); - GLubyte* dstData = mip.pixelData.data(); + GLubyte *dstData = mip.pixelData.data(); - for (GLsizei y = 0; y < height; ++y) { - const GLubyte* srcRow = srcData + y * srcRowStride; - GLubyte* dstRow = dstData + y * dstRowStride; + if (isDefaultUnpack) { + const GLubyte *srcData = static_cast(data); + size_t srcRowSize = width * bytesPerPixel; + size_t srcRowStride = (srcRowSize + unpackAlignment - 1) & ~(unpackAlignment - 1); - if (unpackSwapBytes) { - SwapBytesForTexture_(format, type, srcRow, dstRow, width); + if (srcRowStride == dstRowStride) { + memcpy(dstData, srcData, dstSize); + MG_Util::Debug::LogD( + "MG_State: Texture: Upload2D uploaded %zu bytes with fast path - block memcpy", dstSize); + } else { + for (GLsizei y = 0; y < height; ++y) { + const GLubyte *srcRow = srcData + y * srcRowStride; + GLubyte *dstRow = dstData + y * dstRowStride; + memcpy(dstRow, srcRow, srcRowSize); + } + MG_Util::Debug::LogD( + "MG_State: Texture: Upload2D uploaded %zu bytes with fast path - row memcpy", dstSize); } - else if (unpackLSBFirst && componentSize == 1) { - ReverseBitOrder_(srcRow, dstRow, width * bytesPerPixel); - } - else { - memcpy(dstRow, srcRow, width * bytesPerPixel); + } else { + GLsizei rowLength = (unpackRowLength > 0) ? unpackRowLength : width; + + size_t srcRowSize = rowLength * bytesPerPixel; + size_t srcRowStride = (srcRowSize + unpackAlignment - 1) & ~(unpackAlignment - 1); + size_t srcImageStride = (unpackImageHeight > 0) ? + srcRowStride * unpackImageHeight : + srcRowStride * height; + + const GLubyte *srcData = static_cast(data); + srcData += unpackSkipImages * srcImageStride; + srcData += unpackSkipRows * srcRowStride; + srcData += unpackSkipPixels * bytesPerPixel; + + for (GLsizei y = 0; y < height; ++y) { + const GLubyte *srcRow = srcData + y * srcRowStride; + GLubyte *dstRow = dstData + y * dstRowStride; + + if (unpackSwapBytes) { + SwapBytesForTexture_(format, type, srcRow, dstRow, width); + } else if (unpackLSBFirst && componentSize == 1) { + ReverseBitOrder_(srcRow, dstRow, width * bytesPerPixel); + } else { + memcpy(dstRow, srcRow, width * bytesPerPixel); + } } + mip.hasData = true; + MG_Util::Debug::LogD( + "MG_State: Texture: Upload2D uploaded %zu bytes with unpack params", dstSize); } - mip.hasData = true; - MG_Util::Debug::LogD("MG_State: Texture: Upload2D uploaded %zu bytes with unpack params", dstSize); - } else { - mip.hasData = false; - MG_Util::Debug::LogD("MG_State: Texture: Upload2D data pointer is null"); } - tex.params.mipmapData[level] = mip; +// tex.params.mipmapData[level] = mip; return GL_NO_ERROR; } @@ -364,7 +392,7 @@ GLenum TextureState::UpdateRegion2D(GLenum target, GLint level, GLint xoffset, const size_t bytesPerPixel = CalculateBytesPerPixel_(format, type); const size_t srcSize = CalculatePixelDataSize_(format, type, width, height); - + if (bytesPerPixel == 0) { MG_Util::Debug::LogE("MG_State: Texture: Invalid format/type combination"); return GL_INVALID_ENUM; @@ -402,27 +430,68 @@ GLenum TextureState::UpdateRegion2D(GLenum target, GLint level, GLint xoffset, MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D srcData=%p, dstData=%p", srcData, dstData); MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D bytesPerPixel=%zu", bytesPerPixel); - for (GLsizei y = 0; y < height; ++y) { - const GLubyte* srcRow = srcData + y * srcRowStride; - GLubyte* dstRow = dstData + y * dstRowStride; + // Check if fast path is applicable + const bool isDefaultUnpack = (unpackSwapBytes == 0) && (unpackLSBFirst == 0) && + (unpackRowLength == 0) && (unpackAlignment == 4) && + (unpackSkipPixels == 0) && (unpackSkipRows == 0) && + (unpackImageHeight == 0) && (unpackSkipImages == 0); - if (unpackSwapBytes) { - for (GLsizei x = 0; x < width; ++x) { - const GLubyte* srcPixel = srcRow + x * bytesPerPixel; - GLubyte* dstPixel = dstRow + x * bytesPerPixel; - SwapPixelBytes_(format, type, srcPixel, dstPixel); - } - } else if (unpackLSBFirst && GetComponentSize_(type) == 1) { - for (size_t i = 0; i < width * bytesPerPixel; ++i) { - dstRow[i] = ReverseBits_(srcRow[i]); - } + if (isDefaultUnpack) { + const size_t copySize = width * bytesPerPixel; + + if (srcRowStride == dstRowStride) { + MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D block memcpy(dst=%p, src=%p, size=%zu) called.", dstData, srcData, width * bytesPerPixel); + memcpy(dstData, srcData, copySize * height); } else { - MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D memcpy(dst=%p, src=%p, size=%zu) called.", dstRow, srcRow, width * bytesPerPixel); - memcpy(dstRow, srcRow, width * bytesPerPixel); + for (GLsizei y = 0; y < height; ++y) { + const GLubyte* srcRow = srcData + y * srcRowStride; + GLubyte* dstRow = dstData + y * dstRowStride; + memcpy(dstRow, srcRow, copySize); + MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D row memcpy(dst=%p, src=%p, size=%zu) called.", dstRow, srcRow, width * bytesPerPixel); + } + } + } else { + const size_t componentSize = GetComponentSize_(type); + for (GLsizei y = 0; y < height; ++y) { + const GLubyte* srcRow = srcData + y * srcRowStride; + GLubyte* dstRow = dstData + y * dstRowStride; + + if (unpackSwapBytes) { + for (GLsizei x = 0; x < width; ++x) { + SwapPixelBytes_(format, type, srcRow + x*bytesPerPixel, dstRow + x*bytesPerPixel); + } + } else if (unpackLSBFirst && componentSize == 1) { + for (size_t i = 0; i < width * bytesPerPixel; ++i) { + dstRow[i] = ReverseBits_(srcRow[i]); + } + } else { + memcpy(dstRow, srcRow, width * bytesPerPixel); + } } } +// for (GLsizei y = 0; y < height; ++y) { +// const GLubyte* srcRow = srcData + y * srcRowStride; +// GLubyte* dstRow = dstData + y * dstRowStride; +// +// if (unpackSwapBytes) { +// for (GLsizei x = 0; x < width; ++x) { +// const GLubyte* srcPixel = srcRow + x * bytesPerPixel; +// GLubyte* dstPixel = dstRow + x * bytesPerPixel; +// SwapPixelBytes_(format, type, srcPixel, dstPixel); +// } +// } else if (unpackLSBFirst && GetComponentSize_(type) == 1) { +// for (size_t i = 0; i < width * bytesPerPixel; ++i) { +// dstRow[i] = ReverseBits_(srcRow[i]); +// } +// } else { +// MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D memcpy(dst=%p, src=%p, size=%zu) called.", dstRow, srcRow, width * bytesPerPixel); +// memcpy(dstRow, srcRow, width * bytesPerPixel); +// } +// } + mip.hasData = true; + mip.dirty = true; MG_Util::Debug::LogD("MG_State: Texture: UpdateRegion2D succeeded"); return GL_NO_ERROR; } @@ -541,7 +610,7 @@ GLenum TextureState::Delete(GLuint texture) { if (it != this->textures.end()) { InvalidateTextureInAllUnits_(texture); this->textures.erase(it); - freeIDs_.insert(texture); + freeID_.emplace_back(texture); MG_Util::Debug::LogD("MG_State: Texture: Delete succeeded for texture=%u", texture); } else { MG_Util::Debug::LogW("MG_State: Texture: Delete texture %u not found", texture); @@ -563,7 +632,7 @@ GLenum TextureState::DeleteN(GLsizei n, const GLuint* textures) { if (it != this->textures.end()) { InvalidateTextureInAllUnits_(id); this->textures.erase(it); - freeIDs_.insert(id); + freeID_.emplace_back(id); MG_Util::Debug::LogD("MG_State: Texture: DeleteN deleted texture=%u", id); } else { MG_Util::Debug::LogW("MG_State: Texture: DeleteN texture %u not found", id); @@ -630,7 +699,7 @@ GLenum TextureState::QueryLevelPropertyIntVector(GLenum target, GLint level, GLe return GL_NO_ERROR; } - std::unordered_map::iterator texIt; + unordered_map::iterator texIt; if (!isProxyTexture) { texIt = textures.find(boundTexture); @@ -895,7 +964,7 @@ GLenum TextureState::CheckUpdatingTextureRegion2DValidity_(GLenum target, GLint return GL_INVALID_OPERATION; } - auto tex = textures[boundTex]; + const auto& tex = textures[boundTex]; if (tex.IsImmutable()) { MG_Util::Debug::LogE("MG_State: Texture: CheckUpdatingTextureRegion2DValidity_ texture is immutable"); @@ -934,7 +1003,7 @@ GLenum TextureState::CheckUpdatingTextureRegion2DValidity_(GLenum target, GLint return GL_INVALID_OPERATION; } - TextureParams::MipmapLevel& mip = mipIt->second; + const auto& mip = mipIt->second; if (xoffset + width > mip.width || yoffset + height > mip.height) { MG_Util::Debug::LogE("MG_State: Texture: CheckUpdatingTextureRegion2DValidity_ region out of bounds: " "x=%d+%d > %d or y=%d+%d > %d", diff --git a/MG/MG_GL/State/Texture/TextureState.h b/MG/MG_GL/State/Texture/TextureState.h index 804bc3a8..ea0de3af 100644 --- a/MG/MG_GL/State/Texture/TextureState.h +++ b/MG/MG_GL/State/Texture/TextureState.h @@ -19,8 +19,10 @@ struct ComponentSizes { }; struct TextureParams { - std::unordered_map texPropertiesFloat; - std::unordered_map texPropertiesInt; + template + using unordered_map = ankerl::unordered_dense::map; + unordered_map texPropertiesFloat; + unordered_map texPropertiesInt; struct MipmapLevel { GLsizei width = 0; GLsizei height = 0; @@ -29,8 +31,9 @@ struct TextureParams { GLenum type = GL_UNSIGNED_BYTE; std::vector pixelData; bool hasData = false; + bool dirty = false; // TODO: encapsulate this with an public API to RHI }; - std::unordered_map mipmapData; + unordered_map mipmapData; }; class TextureObject { @@ -44,23 +47,28 @@ public: }; class TextureUnitState { + template + using unordered_map = ankerl::unordered_dense::map; private: GLenum activeTarget = GL_TEXTURE_2D; public: - std::unordered_map boundTextures; + unordered_map boundTextures; void Bind(GLenum target, GLuint texture); GLuint GetBoundTexture(GLenum target); }; class TextureState { + template + using unordered_map = ankerl::unordered_dense::map; private: GLuint activeTextureUnit_ = 0; - GLuint lastUsedID_ = 0; - std::set freeIDs_; + GLuint lastUsedID_ = 1; +// std::unordered_set freeIDs_; + std::vector freeID_; - std::unordered_map proxyTextures_; + unordered_map proxyTextures_; static GLint GetUnpackParam_(GLenum pname); public: @@ -68,7 +76,7 @@ public: bool IsTextureGenerated(GLuint texture); bool IsTexture(GLuint texture); - std::unordered_map textures; + unordered_map textures; // Return: the validity of the operation, according to OpenGL 3 standard GLenum BindUnit(GLenum textureUnit); diff --git a/MG/MG_GL/State/VertexArray/VertexArrayState.cpp b/MG/MG_GL/State/VertexArray/VertexArrayState.cpp index f6626b75..59e9199f 100644 --- a/MG/MG_GL/State/VertexArray/VertexArrayState.cpp +++ b/MG/MG_GL/State/VertexArray/VertexArrayState.cpp @@ -10,6 +10,37 @@ VertexArrayState::VertexArrayState() { vaos_[0]; } +GLenum VertexArrayState::GenName(GLuint *array) { + MG_Util::Debug::LogD("MG_State: VAO: GenName"); + if (!array) + return GL_INVALID_VALUE; + GLuint id; + if (freeIds_.empty()) { + id = ++lastId_; + } else { + id = *freeIds_.begin(); + freeIds_.erase(freeIds_.begin()); + } + *array = id; + MG_Util::Debug::LogD("MG_State: VAO: Generated new name %d", id); + return GL_NO_ERROR; +} + +GLenum VertexArrayState::GenNameN(GLsizei n, GLuint* arrays) { + MG_Util::Debug::LogD("MG_State: VAO: GenNameN called with n=%d", n); + if (n < 0) + return GL_INVALID_VALUE; + for (GLsizei i = 0; i < n; ++i) { + GLenum result = GenName(&arrays[i]); + if (result != GL_NO_ERROR) { + MG_Util::Debug::LogE("MG_State: VAO: GenNameN failed at index %d with error 0x%x", i, result); + return result; + } + } + MG_Util::Debug::LogD("MG_State: VAO: GenNameN created %d names", n); + return GL_NO_ERROR; +} + GLenum VertexArrayState::Create(GLuint* array) { if (array == nullptr) return GL_INVALID_VALUE; @@ -41,24 +72,57 @@ GLenum VertexArrayState::CreateN(GLsizei n, GLuint* arrays) { } GLenum VertexArrayState::Bind(GLuint array) { - if (array != 0 && !vaos_.count(array)) return GL_INVALID_OPERATION; + MG_Util::Debug::LogD("MG_State: VAO: Bind called for %u", array); + if (array != 0) { + if (!ValidateGeneratedName(array)) { + MG_Util::Debug::LogE("MG_State: VAO: Bind invalid name %u", array); + return GL_INVALID_OPERATION; + } + auto& vao = vaos_[array]; + if (!vao.generated) { + MG_Util::Debug::LogD("MG_State: VAO: Creating VAO %u during bind", array); + vao.generated = true; + } + } currentVao_ = array; + MG_Util::Debug::LogD("MG_State: VAO: Bound to %u", array); return GL_NO_ERROR; } +bool VertexArrayState::ValidateGeneratedName(GLuint array) { + if (array == 0) + return true; + bool inFreeList = freeIds_.count(array) > 0; + bool valid = (array <= lastId_) && !inFreeList; + MG_Util::Debug::LogD("MG_State: VAO: ValidateGeneratedName %u: %d", array, valid); + return valid; +} + +bool VertexArrayState::ValidateAllocatedHandle(GLuint array) { + bool exists = vaos_.count(array) && vaos_[array].generated; + MG_Util::Debug::LogD("MG_State: VAO: ValidateAllocatedHandle %u: %d", array, exists); + return exists; +} + GLenum VertexArrayState::EnableAttrib(GLuint index) { - if (currentVao_ == 0) return GL_INVALID_OPERATION; + if (!ValidateAllocatedHandle(currentVao_)) + return GL_INVALID_OPERATION; if (index >= GL_MAX_VERTEX_ATTRIBS) return GL_INVALID_VALUE; GetCurrentVAO()->attribs[index].enabled = true; MG_Util::Debug::LogD("Attrib vaos_[%u].attribs[%u].enabled = %d", currentVao_, index, vaos_[currentVao_].attribs[index].enabled); + + GetCurrentVAO()->attribDirty = true; return GL_NO_ERROR; } GLenum VertexArrayState::DisableAttrib(GLuint index) { - if (currentVao_ == 0) return GL_INVALID_OPERATION; + if (!ValidateAllocatedHandle(currentVao_)) + return GL_INVALID_OPERATION; if (index >= GL_MAX_VERTEX_ATTRIBS) return GL_INVALID_VALUE; GetCurrentVAO()->attribs[index].enabled = false; MG_Util::Debug::LogD("Attrib vaos_[%u].attribs[%u].enabled = %d", currentVao_, index, vaos_[currentVao_].attribs[index].enabled); + + GetCurrentVAO()->attribDirty = true; return GL_NO_ERROR; } @@ -66,7 +130,8 @@ GLenum VertexArrayState::SetAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer, bool isInteger, GLuint currentArrayBuffer) { - if (currentVao_ == 0) return GL_INVALID_OPERATION; + if (!ValidateAllocatedHandle(currentVao_)) + return GL_INVALID_OPERATION; if (index >= GL_MAX_VERTEX_ATTRIBS) return GL_INVALID_VALUE; VertexAttribState state; @@ -81,6 +146,8 @@ GLenum VertexArrayState::SetAttribPointer(GLuint index, GLint size, GLenum type, state.enabled = true; vaos_[currentVao_].attribs[index] = state; + + GetCurrentVAO()->attribDirty = true; return GL_NO_ERROR; } diff --git a/MG/MG_GL/State/VertexArray/VertexArrayState.h b/MG/MG_GL/State/VertexArray/VertexArrayState.h index ac19561c..bfd96981 100644 --- a/MG/MG_GL/State/VertexArray/VertexArrayState.h +++ b/MG/MG_GL/State/VertexArray/VertexArrayState.h @@ -21,6 +21,8 @@ struct VertexAttribState { struct VertexArrayObject { bool generated = false; + bool attribDirty = false; + bool eboDirty = false; GLuint elementBuffer = 0; std::unordered_map attribs; }; @@ -30,6 +32,8 @@ public: VertexArrayState(); // Return: the validity of the operation, according to OpenGL 3 standard + GLenum GenName(GLuint* array); + GLenum GenNameN(GLsizei n, GLuint* arrays); GLenum Create(GLuint* array); GLenum CreateN(GLsizei n, GLuint* arrays); GLenum Bind(GLuint array); @@ -42,6 +46,8 @@ public: GLuint GetBoundElementBuffer(); VertexArrayObject* GetCurrentVAO(); + bool ValidateGeneratedName(GLuint array); + bool ValidateAllocatedHandle(GLuint array); GLuint currentVao_ = 0; std::unordered_map vaos_; diff --git a/MG/MG_UTIL/Debug/Debug.cpp b/MG/MG_UTIL/Debug/Debug.cpp index ddaac4e2..5add9b7e 100644 --- a/MG/MG_UTIL/Debug/Debug.cpp +++ b/MG/MG_UTIL/Debug/Debug.cpp @@ -1206,6 +1206,15 @@ void Log##name(const char* format, ...) { \ CASE(GL_DOT3_RGBA) /* texture_border_clamp */ CASE(GL_CLAMP_TO_BORDER) +/* framebuffer_status */ + CASE(GL_FRAMEBUFFER_COMPLETE) + CASE(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) + CASE(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) + CASE(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) + CASE(GL_FRAMEBUFFER_UNSUPPORTED) + CASE(GL_COLOR_ATTACHMENT0) + CASE(GL_DEPTH_ATTACHMENT) + CASE(GL_STENCIL_ATTACHMENT) /* * Miscellaneous */ diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..534b45db --- /dev/null +++ b/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.library' + +android { + namespace 'top.mobilegl.mobilegl' + compileSdk 34 + + defaultConfig { + minSdk 26 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + } + proguard { + minifyEnabled true + initWith debug + } + fordebug { + debuggable true + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + version "3.22.1" + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + ndkVersion '27.2.12479018' +} diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h new file mode 100644 index 00000000..13484a98 --- /dev/null +++ b/include/ankerl/unordered_dense.h @@ -0,0 +1,2101 @@ +///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// + +// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. +// Version 4.5.0 +// https://github.com/martinus/unordered_dense +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2024 Martin Leitner-Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ANKERL_UNORDERED_DENSE_H +#define ANKERL_UNORDERED_DENSE_H + +// see https://semver.org/spec/v2.0.0.html +#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes +#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 5 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality +#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes + +// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/ + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) +#define ANKERL_UNORDERED_DENSE_NAMESPACE \ + ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \ + ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH) + +#if defined(_MSVC_LANG) +# define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG +#else +# define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus +#endif + +#if defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__)) +#elif defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) +#endif + +// exceptions +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 // NOLINT(cppcoreguidelines-macro-usage) +#else +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 // NOLINT(cppcoreguidelines-macro-usage) +#endif +#ifdef _MSC_VER +# define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline) +#else +# define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline)) +#endif + +// defined in unordered_dense.cpp +#if !defined(ANKERL_UNORDERED_DENSE_EXPORT) +# define ANKERL_UNORDERED_DENSE_EXPORT +#endif + +#if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L +# error ankerl::unordered_dense requires C++17 or higher +#else +# include // for array +# include // for uint64_t, uint32_t, uint8_t, UINT64_C +# include // for size_t, memcpy, memset +# include // for equal_to, hash +# include // for initializer_list +# include // for pair, distance +# include // for numeric_limits +# include // for allocator, allocator_traits, shared_ptr +# include // for optional +# include // for out_of_range +# include // for basic_string +# include // for basic_string_view, hash +# include // for forward_as_tuple +# include // for enable_if_t, declval, conditional_t, ena... +# include // for forward, exchange, pair, as_const, piece... +# include // for vector +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0 +# include // for abort +# endif + +# if defined(__has_include) && !defined(ANKERL_UNORDERED_DENSE_DISABLE_PMR) +# if __has_include() +# define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# elif __has_include() +# define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# endif +# endif + +# if defined(_MSC_VER) && defined(_M_X64) +# include +# pragma intrinsic(_umul128) +# endif + +# if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +# define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage) +# else +# define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# endif + +namespace ankerl::unordered_dense { +inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE { + +namespace detail { + +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() { + throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found"); +} +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() { + throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size"); +} +[[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() { + throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements"); +} + +# else + +[[noreturn]] inline void on_error_key_not_found() { + abort(); +} +[[noreturn]] inline void on_error_bucket_overflow() { + abort(); +} +[[noreturn]] inline void on_error_too_many_elements() { + abort(); +} + +# endif + +} // namespace detail + +// hash /////////////////////////////////////////////////////////////////////// + +// This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash +// No big-endian support (because different values on different machines don't matter), +// hardcodes seed and the secret, reformats the code, and clang-tidy fixes. +namespace detail::wyhash { + +inline void mum(uint64_t* a, uint64_t* b) { +# if defined(__SIZEOF_INT128__) + __uint128_t r = *a; + r *= *b; + *a = static_cast(r); + *b = static_cast(r >> 64U); +# elif defined(_MSC_VER) && defined(_M_X64) + *a = _umul128(*a, *b, b); +# else + uint64_t ha = *a >> 32U; + uint64_t hb = *b >> 32U; + uint64_t la = static_cast(*a); + uint64_t lb = static_cast(*b); + uint64_t hi{}; + uint64_t lo{}; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + (rm0 << 32U); + auto c = static_cast(t < rl); + lo = t + (rm1 << 32U); + c += static_cast(lo < t); + hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c; + *a = lo; + *b = hi; +# endif +} + +// multiply and xor mix function, aka MUM +[[nodiscard]] inline auto mix(uint64_t a, uint64_t b) -> uint64_t { + mum(&a, &b); + return a ^ b; +} + +// read functions. WARNING: we don't care about endianness, so results are different on big endian! +[[nodiscard]] inline auto r8(const uint8_t* p) -> uint64_t { + uint64_t v{}; + std::memcpy(&v, p, 8U); + return v; +} + +[[nodiscard]] inline auto r4(const uint8_t* p) -> uint64_t { + uint32_t v{}; + std::memcpy(&v, p, 4); + return v; +} + +// reads 1, 2, or 3 bytes +[[nodiscard]] inline auto r3(const uint8_t* p, size_t k) -> uint64_t { + return (static_cast(p[0]) << 16U) | (static_cast(p[k >> 1U]) << 8U) | p[k - 1]; +} + +[[maybe_unused]] [[nodiscard]] inline auto hash(void const* key, size_t len) -> uint64_t { + static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f), + UINT64_C(0xe7037ed1a0b428db), + UINT64_C(0x8ebc6af09c88c6e3), + UINT64_C(0x589965cc75374cc3)}; + + auto const* p = static_cast(key); + uint64_t seed = secret[0]; + uint64_t a{}; + uint64_t b{}; + if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) { + if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) { + a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U)); + b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U)); + } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) { + a = r3(p, len); + b = 0; + } else { + a = 0; + b = 0; + } + } else { + size_t i = len; + if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) { + uint64_t see1 = seed; + uint64_t see2 = seed; + do { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1); + see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2); + p += 48; + i -= 48; + } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48)); + seed ^= see1 ^ see2; + } + while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + i -= 16; + p += 16; + } + a = r8(p + i - 16); + b = r8(p + i - 8); + } + + return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed)); +} + +[[nodiscard]] inline auto hash(uint64_t x) -> uint64_t { + return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15)); +} + +} // namespace detail::wyhash + +ANKERL_UNORDERED_DENSE_EXPORT template +struct hash { + auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) + -> uint64_t { + return std::hash{}(obj); + } +}; + +template +struct hash::is_avalanching> { + using is_avalanching = void; + auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) + -> uint64_t { + return std::hash{}(obj); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string const& str) const noexcept -> uint64_t { + return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size()); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string_view const& sv) const noexcept -> uint64_t { + return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size()); + } +}; + +template +struct hash { + using is_avalanching = void; + auto operator()(T* ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr)); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::unique_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash> { + using is_avalanching = void; + auto operator()(std::shared_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + using is_avalanching = void; + auto operator()(Enum e) const noexcept -> uint64_t { + using underlying = typename std::underlying_type_t; + return detail::wyhash::hash(static_cast(e)); + } +}; + +template +struct tuple_hash_helper { + // Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest. + // If it isn't an integral we need to hash it. + template + [[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t { + if constexpr (std::is_integral_v || std::is_enum_v) { + return static_cast(arg); + } else { + return hash{}(arg); + } + } + + [[nodiscard]] static auto mix64(uint64_t state, uint64_t v) -> uint64_t { + return detail::wyhash::mix(state + v, uint64_t{0x9ddfea08eb382d69}); + } + + // Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If + // not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized + // away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer. + template + [[nodiscard]] static auto calc_hash(T const& t, std::index_sequence) noexcept -> uint64_t { + auto h = uint64_t{}; + ((h = mix64(h, to64(std::get(t)))), ...); + return h; + } +}; + +template +struct hash> : tuple_hash_helper { + using is_avalanching = void; + auto operator()(std::tuple const& t) const noexcept -> uint64_t { + return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); + } +}; + +template +struct hash> : tuple_hash_helper { + using is_avalanching = void; + auto operator()(std::pair const& t) const noexcept -> uint64_t { + return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); + } +}; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \ + template <> \ + struct hash { \ + using is_avalanching = void; \ + auto operator()(T const& obj) const noexcept -> uint64_t { \ + return detail::wyhash::hash(static_cast(obj)); \ + } \ + } + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +# endif +// see https://en.cppreference.com/w/cpp/utility/hash +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char); +# if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L && defined(__cpp_char8_t) +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t); +# endif +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long); +ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long); + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +# endif + +// bucket_type ////////////////////////////////////////////////////////// + +namespace bucket_type { + +struct standard { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + uint32_t m_value_idx; // index into the m_values vector. +}; + +ANKERL_UNORDERED_DENSE_PACK(struct big { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + size_t m_value_idx; // index into the m_values vector. +}); + +} // namespace bucket_type + +namespace detail { + +struct nonesuch {}; +struct default_container_t {}; + +template class Op, class... Args> +struct detector { + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; +}; + +template