diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 4a8b9238..05447c79 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #if defined(__linux__) && !defined(__ANDROID__) && __has_include() #pragma push_macro("Bool") #pragma push_macro("None") @@ -35,6 +38,8 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_External::GLESFunctionsTable g_GLESFuncs; MG_External::GLESCapabilities g_GLESCapabilities; + static Bool QueryCurrentSurfaceSize(Int& outWidth, Int& outHeight); + enum class DrawSyncBit : Uint32 { None = 0, IndexBuffer = 1 << 0, @@ -375,19 +380,30 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace RenderStateImpl { static Uint16 g_syncedRenderStateVersion = 0; + static Bool g_hasSyncedRenderState = false; static RenderStateParameters g_syncedRenderStateParameters; + static IntVec4 g_syncedBackendViewport = IntVec4(-1, -1, -1, -1); void SyncRenderState() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif Uint16 currentRenderStateVersion = MG_State::pGLContext->GetRenderStateParametersVersion(); - if (currentRenderStateVersion == g_syncedRenderStateVersion) return; + if (g_hasSyncedRenderState && currentRenderStateVersion == g_syncedRenderStateVersion) return; const auto& parameters = MG_State::pGLContext->GetRenderStateParameters(); - if (parameters.Viewport != g_syncedRenderStateParameters.Viewport) { - g_GLESFuncs.glViewport(parameters.Viewport.x(), parameters.Viewport.y(), parameters.Viewport.z(), - parameters.Viewport.w()); + IntVec4 backendViewport = parameters.Viewport; + if (backendViewport.z() <= 0 || backendViewport.w() <= 0) { + Int surfaceWidth = 0; + Int surfaceHeight = 0; + if (QueryCurrentSurfaceSize(surfaceWidth, surfaceHeight)) { + backendViewport = IntVec4(0, 0, surfaceWidth, surfaceHeight); + } + } + if (backendViewport != g_syncedBackendViewport) { + g_GLESFuncs.glViewport(backendViewport.x(), backendViewport.y(), backendViewport.z(), + backendViewport.w()); + g_syncedBackendViewport = backendViewport; } #define SYNC_CAPABILITY(cap_mg, cap_gl) \ @@ -552,6 +568,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_syncedRenderStateVersion = currentRenderStateVersion; g_syncedRenderStateParameters = parameters; + g_hasSyncedRenderState = true; } } // namespace RenderStateImpl @@ -1873,6 +1890,73 @@ namespace MobileGL::MG_Backend::DirectGLES { static EGLSurface g_Surface = EGL_NO_SURFACE; static EGLConfig g_Config = nullptr; + static Bool QueryCurrentSurfaceSize(Int& outWidth, Int& outHeight) { + outWidth = 0; + outHeight = 0; + if (!g_EGLFuncs.eglQuerySurface || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE) { + return false; + } + + EGLint width = 0; + EGLint height = 0; + if (!g_EGLFuncs.eglQuerySurface(g_Display, g_Surface, EGL_WIDTH, &width) || + !g_EGLFuncs.eglQuerySurface(g_Display, g_Surface, EGL_HEIGHT, &height) || + width <= 0 || height <= 0) { + return false; + } + + outWidth = static_cast(width); + outHeight = static_cast(height); + return true; + } + + static Bool PresentStatsEnabled() { + static const Bool enabled = [] { + const char* value = std::getenv("MOBILEGL_GLES_PRESENT_STATS"); + return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0; + }(); + return enabled; + } + + static void DumpDefaultFramebufferStats() { + if (!PresentStatsEnabled() || !g_GLESFuncs.glReadPixels || !g_EGLFuncs.eglQuerySurface || + g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE) { + return; + } + + Int width = 0; + Int height = 0; + if (!QueryCurrentSurfaceSize(width, height)) { + return; + } + + GLint viewport[4] = {0, 0, 0, 0}; + g_GLESFuncs.glGetIntegerv(GL_VIEWPORT, viewport); + GLint previousReadFramebuffer = 0; + g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &previousReadFramebuffer); + g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + Vector pixels(static_cast(width) * static_cast(height) * 4); + g_GLESFuncs.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + + SizeT nonBlack = 0; + SizeT nonZeroAlpha = 0; + for (SizeT offset = 0; offset + 3 < pixels.size(); offset += 4) { + if (pixels[offset] != 0 || pixels[offset + 1] != 0 || pixels[offset + 2] != 0) { + ++nonBlack; + } + if (pixels[offset + 3] != 0) { + ++nonZeroAlpha; + } + } + + g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(previousReadFramebuffer)); + std::fprintf(stderr, + "MOBILEGL_GLES_PRESENT_STATS nonBlack=%zu/%zu alpha=%zu/%zu size=%dx%d viewport=%d,%d,%d,%d\n", + nonBlack, pixels.size() / 4, nonZeroAlpha, pixels.size() / 4, width, height, + viewport[0], viewport[1], viewport[2], viewport[3]); + } + #if defined(__linux__) && !defined(__ANDROID__) static void* OpenX11Lib() { void* x11Lib = dlopen("libX11.so.6", RTLD_LOCAL | RTLD_NOW); @@ -2092,6 +2176,10 @@ namespace MobileGL::MG_Backend::DirectGLES { void Present() { if (g_Display != EGL_NO_DISPLAY && g_Surface != EGL_NO_SURFACE) { + DumpDefaultFramebufferStats(); + if (g_GLESFuncs.glFlush) { + g_GLESFuncs.glFlush(); + } g_EGLFuncs.eglSwapBuffers(g_Display, g_Surface); } } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index bb284c22..144aca17 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -27,6 +27,17 @@ namespace MobileGL::MG_Backend::DirectGLES { constexpr Bool PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC = true; + static Uint ResolveBackendEsslVersion() { + const auto& version = g_GLESCapabilities.GLESVersion; + if (version.Major > 3 || (version.Major == 3 && version.Minor >= 2)) { + return 320; + } + if (version.Major == 3 && version.Minor >= 1) { + return 310; + } + return 300; + } + namespace BufferImpl { BackendBufferObject::BackendBufferObject() { #ifdef TRACY_ENABLE @@ -1181,8 +1192,8 @@ namespace MobileGL::MG_Backend::DirectGLES { spvc_compiler_options options; spvcSession.CreateOptions(&options); - // TODO: check ESSL version supported by backend driver - spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320); + spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, + ResolveBackendEsslVersion()); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 7210626a..564c9aee 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -1634,15 +1634,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram( const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) { - const Uint32 backendStateVersion = program.GetBackendStateVersion(); - HashType hash = 0; - if (m_lastLookup.program == &program && m_lastLookup.backendStateVersion == backendStateVersion && - m_lastLookup.flags == flags) { - hash = m_lastLookup.hash; - } else { - hash = ComputeHash(program, flags); - m_lastLookup = {.program = &program, .backendStateVersion = backendStateVersion, .flags = flags, .hash = hash}; - } + const HashType hash = ComputeHash(program, flags); auto it = m_cache.find(hash); if (it != m_cache.end()) { return it->second; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index fef6a2ba..0a39abdc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -16,6 +16,9 @@ #include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" #include "MG_Util/Metrics/TextureMetrics.h" +#include +#include +#include #include namespace MobileGL::MG_Backend::DirectVulkan { @@ -74,6 +77,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { return uniformUnit >= 0 ? uniformUnit : 0; } + static Bool ShouldDumpDescriptorStats() { + static const Bool enabled = [] { + const char* value = std::getenv("MOBILEGL_DESCRIPTOR_STATS"); + return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0; + }(); + return enabled; + } + Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager, ProgramFactory* programFactory, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, @@ -262,6 +273,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { .imageView = resource->fullView, .imageLayout = resource->layout, }; + if (ShouldDumpDescriptorStats()) { + std::fprintf(stderr, + "MOBILEGL_DESCRIPTOR_STATS program=%u binding=%u name=%s location=%d unit=%d " + "texture=%u target=%d sampler=%p imageView=%p layout=%d\n", + program.GetExternalIndex(), + binding, + programObj.samplerNameByBinding[binding].c_str(), + location, + unit, + texture->GetExternalIndex(), + static_cast(texture->GetTarget()), + reinterpret_cast(outImageInfo.sampler), + reinterpret_cast(outImageInfo.imageView), + static_cast(outImageInfo.imageLayout)); + } return outImageInfo.sampler != VK_NULL_HANDLE; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 382d4519..d48ba0fe 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -12,6 +12,9 @@ #include "MG_Util/Converters/MGToStr/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" +#include +#include +#include #include namespace MobileGL::MG_Backend::DirectVulkan { @@ -316,6 +319,99 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + static Bool ShouldDumpTextureUploadStats() { + static const Bool enabled = [] { + const char* value = std::getenv("MOBILEGL_TEXTURE_UPLOAD_STATS"); + return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0; + }(); + return enabled; + } + + static void DumpTextureSyncStats(Int textureId, TextureInternalFormat format, TextureUploadTarget uploadTarget, + Uint32 mipLevelCount, const IntVec3& texelSize, SizeT byteSize, + Bool hasDirtyMipLevel) { + if (!ShouldDumpTextureUploadStats()) { + return; + } + + std::fprintf(stderr, + "MOBILEGL_TEXTURE_SYNC_STATS texture=%d format=%s target=%s mips=%u size=%dx%dx%d " + "bytes=%zu dirty=%d\n", + textureId, + MG_Util::ConvertTextureInternalFormatToString(format).c_str(), + MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), + mipLevelCount, + texelSize.x(), + texelSize.y(), + texelSize.z(), + byteSize, + hasDirtyMipLevel ? 1 : 0); + } + + static void DumpTextureUploadStats(Int textureId, TextureUploadTarget target, Uint32 level, + const IntVec3& texelSize, const void* data, SizeT byteSize, Uint32 channels) { + if (!ShouldDumpTextureUploadStats() || data == nullptr || byteSize == 0 || channels == 0) { + return; + } + + const auto* bytes = static_cast(data); + Uint8 minValue = 255; + Uint8 maxValue = 0; + SizeT nonZero = 0; + Uint64 sum = 0; + Uint64 neighborDiff = 0; + SizeT neighborCount = 0; + + for (SizeT i = 0; i < byteSize; ++i) { + minValue = std::min(minValue, bytes[i]); + maxValue = std::max(maxValue, bytes[i]); + nonZero += bytes[i] != 0 ? 1 : 0; + sum += bytes[i]; + } + + const SizeT width = static_cast(std::max(texelSize.x(), 0)); + const SizeT height = static_cast(std::max(texelSize.y(), 0)); + const SizeT pixelStride = channels; + if (width > 1 && height > 0 && byteSize >= width * height * pixelStride) { + for (SizeT y = 0; y < height; ++y) { + const SizeT row = y * width * pixelStride; + for (SizeT x = 1; x < width; ++x) { + const SizeT prev = row + (x - 1) * pixelStride; + const SizeT cur = row + x * pixelStride; + for (SizeT c = 0; c < std::min(channels, 3); ++c) { + neighborDiff += static_cast( + std::abs(static_cast(bytes[cur + c]) - static_cast(bytes[prev + c]))); + ++neighborCount; + } + } + } + } + + const double avg = byteSize > 0 ? static_cast(sum) / static_cast(byteSize) : 0.0; + const double avgNeighborDiff = + neighborCount > 0 ? static_cast(neighborDiff) / static_cast(neighborCount) : 0.0; + std::fprintf(stderr, + "MOBILEGL_TEXTURE_UPLOAD_STATS texture=%d target=%s level=%u size=%dx%dx%d bytes=%zu " + "channels=%u min=%u max=%u avg=%.2f nonzero=%zu neighborDiff=%.2f first=%u,%u,%u,%u\n", + textureId, + MG_Util::ConvertTextureUploadTargetToString(target).c_str(), + level, + texelSize.x(), + texelSize.y(), + texelSize.z(), + byteSize, + channels, + static_cast(minValue), + static_cast(maxValue), + avg, + nonZero, + avgNeighborDiff, + byteSize > 0 ? static_cast(bytes[0]) : 0u, + byteSize > 1 ? static_cast(bytes[1]) : 0u, + byteSize > 2 ? static_cast(bytes[2]) : 0u, + byteSize > 3 ? static_cast(bytes[3]) : 0u); + } + static VkComponentSwizzle ToVkComponentSwizzle(TextureSwizzleParam swizzle) { switch (swizzle) { case TextureSwizzleParam::Red: @@ -777,6 +873,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { break; } } + DumpTextureSyncStats(texture.GetExternalIndex(), texture.GetFormat(), uploadTarget, mipLevelCount, + texelSize, byteSize, hasDirtyMipLevel); if (!hasDirtyMipLevel) { return true; } @@ -1001,6 +1099,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { DeferViewRelease(resource.fullView); resource.fullView = VK_NULL_HANDLE; } + for (auto& sampledView : resource.perMipSampledViews) { + if (sampledView != VK_NULL_HANDLE) { + DeferViewRelease(sampledView); + sampledView = VK_NULL_HANDLE; + } + } const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo); @@ -1111,6 +1215,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!uploadItems.back().expandedData.empty()) { uploadItems.back().source = uploadItems.back().expandedData.data(); } + DumpTextureUploadStats(mipmapTexture.GetExternalIndex(), uploadItems.back().target, + uploadItems.back().level, uploadItems.back().texelSize, + uploadItems.back().source, uploadItems.back().uploadByteSize, + formatInfo.expandRgbToRgba ? 4u : 0u); stagingSize += static_cast(uploadItems.back().uploadByteSize); } } @@ -1156,16 +1264,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture)"); const VkImageAspectFlags aspectMask = GetAspectMaskForFormat(outResource.format); + VkPipelineStageFlags uploadSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags uploadSrcAccessMask = 0; + GetImageTransitionSourceState(outResource.layout, uploadSrcStageMask, uploadSrcAccessMask); Bool ok = TransitionImageLayout(commandBuffer, outResource.image, - outResource.layout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? - kGraphicsSampledReadStages : - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0, - VK_ACCESS_TRANSFER_WRITE_BIT, - aspectMask, 0, outResource.mipLevels, outResource.arrayLayers); + outResource.layout, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + uploadSrcStageMask, + VK_PIPELINE_STAGE_TRANSFER_BIT, + uploadSrcAccessMask, + VK_ACCESS_TRANSFER_WRITE_BIT, + aspectMask, 0, outResource.mipLevels, outResource.arrayLayers); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed"); for (const auto& item : uploadItems) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index aae87450..29455b7c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -20,6 +20,9 @@ #include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" #include "MG_Util/Metrics/TextureMetrics.h" +#include +#include +#include #include namespace MobileGL::MG_Backend::DirectVulkan { @@ -376,6 +379,94 @@ namespace MobileGL::MG_Backend::DirectVulkan { return attributeMask; } + static Bool ShouldDumpVertexInputStats() { + static const Bool enabled = [] { + const char* value = std::getenv("MOBILEGL_VERTEX_INPUT_STATS"); + return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0; + }(); + return enabled; + } + + static const char* PresentDumpPath() { + const char* value = std::getenv("MOBILEGL_PRESENT_DUMP_PATH"); + return value != nullptr && value[0] != '\0' ? value : nullptr; + } + + static void DumpVertexInputStats(Uint32 location, const MG_State::GLState::VertexAttribute& attr, + Uint32 firstVertex, Uint32 vertexCount) { + if (!ShouldDumpVertexInputStats() || attr.Type != DataType::Float32 || attr.Size <= 0 || attr.Size > 4) { + return; + } + + const SizeT componentSize = sizeof(float); + const SizeT elementSize = componentSize * static_cast(attr.Size); + const SizeT stride = attr.Stride > 0 ? static_cast(attr.Stride) : elementSize; + const Uint8* base = nullptr; + SizeT available = 0; + if (attr.Buffer) { + const auto& data = attr.Buffer->GetDataReadOnly(); + if (!data || attr.Offset >= data->size()) { + return; + } + base = data->data() + attr.Offset; + available = data->size() - attr.Offset; + } else { + base = reinterpret_cast(attr.Offset); + available = static_cast(firstVertex + vertexCount) * stride; + } + if (base == nullptr || vertexCount == 0 || available < elementSize) { + return; + } + + float minValues[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float maxValues[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + Bool initialized = false; + SizeT sampled = 0; + const Uint32 maxSamples = std::min(vertexCount, 256); + for (Uint32 sample = 0; sample < maxSamples; ++sample) { + const SizeT offset = static_cast(firstVertex + sample) * stride; + if (offset + elementSize > available) { + break; + } + + const auto* values = reinterpret_cast(base + offset); + for (Int component = 0; component < attr.Size; ++component) { + if (!initialized) { + minValues[component] = values[component]; + maxValues[component] = values[component]; + } else { + minValues[component] = std::min(minValues[component], values[component]); + maxValues[component] = std::max(maxValues[component], values[component]); + } + } + initialized = true; + ++sampled; + } + + if (!initialized) { + return; + } + + std::fprintf(stderr, + "MOBILEGL_VERTEX_INPUT_STATS loc=%u buffer=%u size=%d stride=%zu first=%u count=%u sampled=%zu " + "min=(%.3f,%.3f,%.3f,%.3f) max=(%.3f,%.3f,%.3f,%.3f)\n", + location, + attr.Buffer ? attr.Buffer->GetExternalIndex() : 0u, + attr.Size, + stride, + firstVertex, + vertexCount, + sampled, + minValues[0], + minValues[1], + minValues[2], + minValues[3], + maxValues[0], + maxValues[1], + maxValues[2], + maxValues[3]); + } + static Bool TryGetCurrentVertexAttributeFormat(GLenum glType, VkFormat& outFormat) { switch (glType) { case GL_FLOAT: @@ -1398,12 +1489,17 @@ void main() { if (binding >= vertexInputState.bindings.size()) { break; } + const Uint32 bindingLocation = binding < vertexInputState.bindingAttributeLocations.size() + ? vertexInputState.bindingAttributeLocations[binding] + : static_cast(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS); + if (bindingLocation < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS) { + DumpVertexInputStats(bindingLocation, vao.GetAttribute(bindingLocation), + drawParams.firstVertex, drawParams.vertexCount); + } const Bool usesClientMemory = binding < vertexInputState.bindingUsesClientMemory.size() && vertexInputState.bindingUsesClientMemory[binding]; if (usesClientMemory) { - const Uint32 location = binding < vertexInputState.bindingAttributeLocations.size() - ? vertexInputState.bindingAttributeLocations[binding] - : static_cast(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS); + const Uint32 location = bindingLocation; MOBILEGL_ASSERT(location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS, "UploadAndBindVertexStreams failed to resolve client attribute location"); @@ -3840,7 +3936,8 @@ void main() { VkBufferObject presentStatsReadback; VkDeviceSize presentStatsReadbackSize = 0; const VkExtent2D presentStatsExtent = m_swapchainObject.GetExtent(); - const Bool collectPresentStats = PresentStatsEnabled() && frame.isCommandRecording && + const char* presentDumpPath = PresentDumpPath(); + const Bool collectPresentStats = (PresentStatsEnabled() || presentDumpPath != nullptr) && frame.isCommandRecording && presentStatsExtent.width > 0 && presentStatsExtent.height > 0; if (collectPresentStats) { presentStatsReadbackSize = static_cast(presentStatsExtent.width) * @@ -3909,6 +4006,7 @@ void main() { MOBILEGL_ASSERT(pixels != nullptr, "Present stats: failed to map readback buffer"); SizeT nonBlack = 0; SizeT nonTransparent = 0; + SizeT colored = 0; const SizeT pixelCount = static_cast(presentStatsExtent.width) * static_cast(presentStatsExtent.height); for (SizeT i = 0; i < pixelCount; ++i) { @@ -3916,14 +4014,33 @@ void main() { if (p[0] != 0 || p[1] != 0 || p[2] != 0) { ++nonBlack; } + const Uint8 minRgb = std::min(p[0], std::min(p[1], p[2])); + const Uint8 maxRgb = std::max(p[0], std::max(p[1], p[2])); + if (maxRgb - minRgb > 24) { + ++colored; + } if (p[3] != 0) { ++nonTransparent; } } - std::fprintf(stderr, - "MOBILEGL_PRESENT_STATS nonBlack=%zu/%zu alpha=%zu/%zu size=%ux%u\n", - nonBlack, pixelCount, nonTransparent, pixelCount, - presentStatsExtent.width, presentStatsExtent.height); + if (PresentStatsEnabled()) { + std::fprintf(stderr, + "MOBILEGL_PRESENT_STATS nonBlack=%zu/%zu colored=%zu/%zu alpha=%zu/%zu size=%ux%u\n", + nonBlack, pixelCount, colored, pixelCount, nonTransparent, pixelCount, + presentStatsExtent.width, presentStatsExtent.height); + } + if (presentDumpPath != nullptr) { + FILE* dump = std::fopen(presentDumpPath, "wb"); + if (dump != nullptr) { + std::fprintf(dump, "P6\n%u %u\n255\n", presentStatsExtent.width, presentStatsExtent.height); + for (SizeT i = 0; i < pixelCount; ++i) { + const Uint8* p = pixels + i * 4; + const Uint8 rgb[3] = {p[0], p[1], p[2]}; + std::fwrite(rgb, 1, sizeof(rgb), dump); + } + std::fclose(dump); + } + } } frame.isCommandRecording = false; frame.hasCommandBufferRecorded = false;