diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 101adccd..d589a76f 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -8078,6 +8078,38 @@ namespace MobileGL::MG_Backend::DirectGLES { } static Bool EnsureGenerateMipmapStorageAllocated(const SharedPtr& texture) { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + // The frontend has already defined the generated chain before emitting this verb. + // Its resource_respecify carries that shape, so the server only verifies the + // descriptor; it must not allocate or dirty the client's level shadows again. + // Identity resolution is the same existing registry lookup used by texture sync. + const auto handle = TextureImpl::g_backendTextureObjects.HandleOf(texture.get()); + const auto* record = PipeTextureRecordForHandle(handle); + if (record == nullptr || record->Desc.Width == 0 || record->Desc.Levels == 0) { + MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage"); + } + const auto& desc = record->Desc; + Uint maxDimension = desc.Width; + const auto target = static_cast(desc.Target); + if (target != MG_Pipe::MGPipeResourceTarget::Tex1D && + target != MG_Pipe::MGPipeResourceTarget::Tex1DArray) { + maxDimension = std::max(maxDimension, desc.Height); + } + if (target == MG_Pipe::MGPipeResourceTarget::Tex3D) { + maxDimension = std::max(maxDimension, desc.Depth); + } + Uint requiredLevels = 1; + while (maxDimension > 1) { + maxDimension /= 2; + ++requiredLevels; + } + if (desc.Levels < requiredLevels) { + MG_Pipe::MGPipeUnmigratedEmulation("generate-mipmap-storage"); + } + return false; // No server-side shadow allocation was necessary. + } +#endif auto* mipmapTexture = dynamic_cast(texture.get()); MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires mipmap texture storage."); Bool allocatedStorage = false; diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index a5a35e2b..cdc2d15b 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2159,7 +2159,7 @@ endif() # f1: split-only source keeps the monolith registry unchanged. if (MOBILEGL_BUILD_DISAGGREGATED) target_sources(MobileGLIntegrationTest PRIVATE Scenarios/F1WireScenario.cpp) - foreach(f1Slot ClearBufferfi ClearBufferfv ClearBufferiv ClearBufferuiv ClearNamedFramebufferfi ClearNamedFramebufferfv ClearNamedFramebufferiv ClearNamedFramebufferuiv CopyTexImage2D CopyTexSubImage2D GenerateMipmap) + foreach(f1Slot ClearBufferfi ClearBufferfv ClearBufferiv ClearBufferuiv ClearNamedFramebufferfi ClearNamedFramebufferfv ClearNamedFramebufferiv ClearNamedFramebufferuiv CopyTexImage2D CopyTexSubImage2D GenerateMipmap GenerateMipmapPackedFloat GenerateMipmapDepth) gtest_discover_tests(MobileGLIntegrationTest TEST_PREFIX "DirectGLES.Split.F1." TEST_FILTER "F1WireScenario.${f1Slot}Pixels" diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index e681c1f2..acc02e95 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -219,6 +219,46 @@ TEST_F(F1WireScenario, GenerateMipmapPixels) { const int expected[4] = {64, 128, 191, 255}; for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.GenerateMipmap.pixels"; } +TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) { + if (!Ready()) return; + // Only level zero exists initially. Generating the special-format chain must use the + // shape published by the client, and level two must contain the generated GPU pixels. + std::array source{}; + for (size_t i = 0; i < source.size(); i += 3) { + source[i] = 0.25f; source[i + 1] = 0.5f; source[i + 2] = 0.75f; + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F, 8, 8, 0, GL_RGB, GL_FLOAT, source.data()); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLfloat pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_FLOAT, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const GLfloat expected[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 0.01f); +} + +TEST_F(F1WireScenario, GenerateMipmapDepthPixels) { + if (!Ready()) return; + std::array source{}; + source.fill(0.375f); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 8, 8, 0, + GL_DEPTH_COMPONENT, GL_FLOAT, source.data()); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture, 2); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLfloat pixel = 0; + glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_NEAR(pixel, 0.375f, 0.00001f); +} + TEST_F(F1WireScenario, NamedBlitPreservesBindingsAndRestoresNextVerbsPixels) { if (!Ready()) return; Attach(GL_RGBA8);