From 70095e458b1eea88e534d1fe7c762f3c615e47c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 12 Sep 2026 13:16:17 -0600 Subject: [PATCH] thin3d: add sub-rectangle texture updates, use them for imgui fonts imgui 1.92 hands the backend a list of dirty rectangles when its font atlas grows, but thin3d could only replace a whole mip level, so every new glyph re-uploaded the entire atlas. Adds DrawContext::UpdateTextureRegions, taking a batch of regions so each backend can submit them together: - Vulkan: packs the regions into the push pool and issues a single vkCmdCopyBufferToImage from the init command buffer, transitioning only the level being written. - OpenGL: new TEXTURE_SUBIMAGE init step. The existing sub-image path is a render command needing an active render pass and a texture slot, which doesn't fit here. Rows are packed caller-side since GLES2 lacks GL_UNPACK_ROW_LENGTH. - D3D11: UpdateSubresource with a box, no staging texture needed. Co-Authored-By: Claude Opus 5 (1M context) --- Common/GPU/D3D11/thin3d_d3d11.cpp | 23 +++++++++++++ Common/GPU/OpenGL/GLQueueRunner.cpp | 34 ++++++++++++++++++++ Common/GPU/OpenGL/GLQueueRunner.h | 12 +++++++ Common/GPU/OpenGL/GLRenderManager.h | 17 ++++++++++ Common/GPU/OpenGL/thin3d_gl.cpp | 24 ++++++++++++++ Common/GPU/Vulkan/VulkanImage.cpp | 12 ++++--- Common/GPU/Vulkan/VulkanImage.h | 2 ++ Common/GPU/Vulkan/thin3d_vulkan.cpp | 50 +++++++++++++++++++++++++++++ Common/GPU/thin3d.h | 16 +++++++++ ext/imgui/imgui_impl_thin3d.cpp | 23 ++++++++++--- 10 files changed, 204 insertions(+), 9 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index 515e98c1db..5d1529ccdf 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -112,6 +112,7 @@ public: void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override; + void UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, Aspect aspects, const char *tag) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, Aspect aspects, FBBlitFilter filter, const char *tag) override; @@ -890,6 +891,7 @@ public: bool CreateStagingTexture(ID3D11Device *device); void UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t *const *data, TextureCallback initDataCallback, int numLevels); + void UpdateTextureRegions(ID3D11DeviceContext *context, int level, const TextureRegionUpdate *regions, int numRegions); ID3D11ShaderResourceView *View() { return view_.Get(); } @@ -1051,6 +1053,22 @@ void D3D11Texture::UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Devic stagingTex_.Reset(); } +void D3D11Texture::UpdateTextureRegions(ID3D11DeviceContext *context, int level, const TextureRegionUpdate *regions, int numRegions) { + const UINT pixelSize = (UINT)DataFormatSizeInBytes(format_); + for (int i = 0; i < numRegions; i++) { + const TextureRegionUpdate ®ion = regions[i]; + D3D11_BOX box{}; + box.left = region.x; + box.top = region.y; + box.front = 0; + box.right = region.x + region.w; + box.bottom = region.y + region.h; + box.back = 1; + const UINT srcStride = region.byteStride ? (UINT)region.byteStride : region.w * pixelSize; + context->UpdateSubresource(tex_.Get(), level, &box, region.data, srcStride, 0); + } +} + Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) { if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) { // D3D11 does not support this format as a texture format. @@ -1077,6 +1095,11 @@ void D3D11DrawContext::UpdateTextureLevels(Texture *texture, const uint8_t **dat tex->UpdateTextureLevels(context_.Get(), device_.Get(), texture, data, initDataCallback, numLevels); } +void D3D11DrawContext::UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) { + D3D11Texture *tex = (D3D11Texture *)texture; + tex->UpdateTextureRegions(context_.Get(), level, regions, numRegions); +} + ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize, const char *tag) { if (language != ShaderLanguage::HLSL_D3D11) { ERROR_LOG(Log::G3D, "Unsupported shader language"); diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 1c7043475d..c302f3c9ec 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -130,6 +130,15 @@ void GLQueueRunner::RunInitSteps(const FastVec &steps, bool skipGLC } break; } + case GLRInitStepType::TEXTURE_SUBIMAGE: + { + if (step.texture_subimage.allocType == GLRAllocType::ALIGNED) { + FreeAlignedMemory(step.texture_subimage.data); + } else if (step.texture_subimage.allocType == GLRAllocType::NEW) { + delete[] step.texture_subimage.data; + } + break; + } case GLRInitStepType::CREATE_PROGRAM: { WARN_LOG(Log::G3D, "CREATE_PROGRAM found with skipGLCalls, not good"); @@ -400,6 +409,31 @@ void GLQueueRunner::RunInitSteps(const FastVec &steps, bool skipGLC CHECK_GL_ERROR_IF_DEBUG(); break; } + case GLRInitStepType::TEXTURE_SUBIMAGE: + { + GLRTexture *tex = step.texture_subimage.texture; + CHECK_GL_ERROR_IF_DEBUG(); + if (boundTexture != tex->texture) { + glBindTexture(tex->target, tex->texture); + boundTexture = tex->texture; + } + _assert_(tex->target == GL_TEXTURE_2D); + _assert_(step.texture_subimage.data != nullptr); + GLenum internalFormat, format, type; + int alignment; + Thin3DFormatToGLFormatAndType(step.texture_subimage.format, internalFormat, format, type, alignment); + glTexSubImage2D(tex->target, step.texture_subimage.level, + step.texture_subimage.x, step.texture_subimage.y, + step.texture_subimage.width, step.texture_subimage.height, + format, type, step.texture_subimage.data); + if (step.texture_subimage.allocType == GLRAllocType::ALIGNED) { + FreeAlignedMemory(step.texture_subimage.data); + } else if (step.texture_subimage.allocType == GLRAllocType::NEW) { + delete[] step.texture_subimage.data; + } + CHECK_GL_ERROR_IF_DEBUG(); + break; + } case GLRInitStepType::TEXTURE_FINALIZE: { CHECK_GL_ERROR_IF_DEBUG(); diff --git a/Common/GPU/OpenGL/GLQueueRunner.h b/Common/GPU/OpenGL/GLQueueRunner.h index 5cf0059b57..e7934ecfde 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.h +++ b/Common/GPU/OpenGL/GLQueueRunner.h @@ -214,6 +214,7 @@ enum class GLRInitStepType : uint8_t { CREATE_FRAMEBUFFER, TEXTURE_IMAGE, + TEXTURE_SUBIMAGE, TEXTURE_FINALIZE, BUFFER_SUBDATA, }; @@ -266,6 +267,17 @@ struct GLRInitStep { bool linearFilter; uint8_t *data; // owned, delete[]-d } texture_image; + struct { + GLRTexture *texture; + Draw::DataFormat format; + int level; + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; + GLRAllocType allocType; + uint8_t *data; // owned, delete[]-d + } texture_subimage; struct { GLRTexture *texture; int loadedLevels; diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index a58a49c3fb..2e03897320 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -444,6 +444,23 @@ public: step.texture_image.linearFilter = linearFilter; } + // Takes ownership over the data pointer and delete[]-s it. Runs as an init step, so unlike + // TextureSubImage below, it doesn't have to happen inside a render pass. + void TextureSubImageInit(GLRTexture *texture, int level, int x, int y, int width, int height, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW) { + std::lock_guard lock(initStepsMutex_); + GLRInitStep &step = initSteps_.push_uninitialized(); + step.stepType = GLRInitStepType::TEXTURE_SUBIMAGE; + step.texture_subimage.texture = texture; + step.texture_subimage.data = data; + step.texture_subimage.format = format; + step.texture_subimage.level = level; + step.texture_subimage.x = x; + step.texture_subimage.y = y; + step.texture_subimage.width = width; + step.texture_subimage.height = height; + step.texture_subimage.allocType = allocType; + } + void TextureSubImage(int slot, GLRTexture *texture, int level, int x, int y, int width, int height, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW) { _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); GLRRenderData _data(GLRRenderCommand::TEXTURE_SUBIMAGE); diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 2b9c3ba037..b1e4f66e68 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -377,6 +377,7 @@ public: void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override; + void UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, Aspect aspects, const char *tag) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, Aspect aspects, FBBlitFilter filter, const char *tag) override; @@ -860,6 +861,7 @@ public: } void UpdateTextureLevels(GLRenderManager *render, const uint8_t *const *data, int numLevels, TextureCallback initDataCallback); + void UpdateTextureRegions(GLRenderManager *render, int level, const TextureRegionUpdate *regions, int numRegions); private: void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback); @@ -920,6 +922,23 @@ void OpenGLTexture::UpdateTextureLevels(GLRenderManager *render, const uint8_t * render->FinalizeTexture(tex_, mipLevels_, genMips); } +void OpenGLTexture::UpdateTextureRegions(GLRenderManager *render, int level, const TextureRegionUpdate *regions, int numRegions) { + const int pixelSize = (int)DataFormatSizeInBytes(format_); + for (int i = 0; i < numRegions; i++) { + const TextureRegionUpdate ®ion = regions[i]; + _dbg_assert_(region.w > 0 && region.h > 0); + const int srcStride = region.byteStride ? region.byteStride : region.w * pixelSize; + const int dstStride = region.w * pixelSize; + // glTexSubImage2D could take the stride through GL_UNPACK_ROW_LENGTH, but that's not in GLES2, + // and the queue runner owns the data anyway, so just pack it here. + uint8_t *texData = new uint8_t[(size_t)dstStride * region.h]; + for (int y = 0; y < region.h; y++) { + memcpy(texData + (size_t)dstStride * y, region.data + (size_t)srcStride * y, dstStride); + } + render->TextureSubImageInit(tex_, level, region.x, region.y, region.w, region.h, format_, texData); + } +} + OpenGLTexture::~OpenGLTexture() { if (tex_) { render_->DeleteTexture(tex_); @@ -1045,6 +1064,11 @@ void OpenGLContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, tex->UpdateTextureLevels(&renderManager_, data, numLevels, initDataCallback); } +void OpenGLContext::UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) { + OpenGLTexture *tex = (OpenGLTexture *)texture; + tex->UpdateTextureRegions(&renderManager_, level, regions, numRegions); +} + DepthStencilState *OpenGLContext::CreateDepthStencilState(const DepthStencilStateDesc &desc) { OpenGLDepthStencilState *ds = new OpenGLDepthStencilState(); ds->depthTestEnabled = desc.depthTestEnabled; diff --git a/Common/GPU/Vulkan/VulkanImage.cpp b/Common/GPU/Vulkan/VulkanImage.cpp index fff06a2d18..35cc082a92 100644 --- a/Common/GPU/Vulkan/VulkanImage.cpp +++ b/Common/GPU/Vulkan/VulkanImage.cpp @@ -170,15 +170,19 @@ bool VulkanTexture::CreateDirect(int w, int h, int depth, int numMips, VkFormat } void VulkanTexture::CopyBufferToMipLevel(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) { + CopyBufferToMipLevelRegion(cmd, copyBatch, mip, 0, 0, mipWidth, mipHeight, depthLayer, buffer, offset, rowLength); +} + +void VulkanTexture::CopyBufferToMipLevelRegion(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int x, int y, int w, int h, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength) { VkBufferImageCopy ©_region = copyBatch->copies.push_uninitialized(); copy_region.bufferOffset = offset; copy_region.bufferRowLength = (uint32_t)rowLength; copy_region.bufferImageHeight = 0; // 2D - copy_region.imageOffset.x = 0; - copy_region.imageOffset.y = 0; + copy_region.imageOffset.x = x; + copy_region.imageOffset.y = y; copy_region.imageOffset.z = depthLayer; - copy_region.imageExtent.width = mipWidth; - copy_region.imageExtent.height = mipHeight; + copy_region.imageExtent.width = w; + copy_region.imageExtent.height = h; copy_region.imageExtent.depth = 1; copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copy_region.imageSubresource.mipLevel = mip; diff --git a/Common/GPU/Vulkan/VulkanImage.h b/Common/GPU/Vulkan/VulkanImage.h index 59b39c232d..e087a8a1c1 100644 --- a/Common/GPU/Vulkan/VulkanImage.h +++ b/Common/GPU/Vulkan/VulkanImage.h @@ -36,6 +36,8 @@ public: // Can also be used to copy individual levels of a 3D texture. // If possible, will just add to the batch instead of submitting a copy. void CopyBufferToMipLevel(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int mipWidth, int mipHeight, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels + // Same, but only covers a rectangle of the mip level instead of all of it. + void CopyBufferToMipLevelRegion(VkCommandBuffer cmd, TextureCopyBatch *copyBatch, int mip, int x, int y, int w, int h, int depthLayer, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels void FinishCopyBatch(VkCommandBuffer cmd, TextureCopyBatch *copyBatch); void GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index a49c91f695..a724f61eaf 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -332,6 +332,7 @@ public: } bool Create(VkCommandBuffer cmd, VulkanBarrierBatch *postBarriers, VulkanPushPool *pushBuffer, const TextureDesc &desc); void Update(VkCommandBuffer cmd, VulkanBarrierBatch *postBarriers, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels); + void UpdateRegions(VkCommandBuffer cmd, VulkanBarrierBatch *postBarriers, VulkanPushPool *pushBuffer, int level, const TextureRegionUpdate *regions, int numRegions); ~VKTexture() { Destroy(); @@ -449,6 +450,7 @@ public: void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override; + void UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, Aspect aspects, const char *tag) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, Aspect aspects, FBBlitFilter filter, const char *tag) override; @@ -860,6 +862,37 @@ void VKTexture::Update(VkCommandBuffer cmd, VulkanBarrierBatch *postBarriers, Vu vkTex_->RestoreAfterTransferDst(numLevels, postBarriers); } +void VKTexture::UpdateRegions(VkCommandBuffer cmd, VulkanBarrierBatch *postBarriers, VulkanPushPool *pushBuffer, int level, const TextureRegionUpdate *regions, int numRegions) { + VkFormat vulkanFormat = DataFormatToVulkan(format_); + int bpp = GetBpp(vulkanFormat); + _dbg_assert_(bpp != 0); + const int bytesPerPixel = bpp / 8; + + // Only the level we're writing needs to change layout, the others stay sampleable. + vkTex_->PrepareForTransferDst(cmd, level + 1); + + TextureCopyBatch batch; + batch.reserve(numRegions); + for (int i = 0; i < numRegions; i++) { + const TextureRegionUpdate ®ion = regions[i]; + _dbg_assert_(region.w > 0 && region.h > 0); + const int srcStride = region.byteStride ? region.byteStride : region.w * bytesPerPixel; + const int dstStride = region.w * bytesPerPixel; + + uint32_t offset; + VkBuffer buf; + uint8_t *dest = (uint8_t *)pushBuffer->Allocate((size_t)dstStride * region.h, 16, &buf, &offset); + _assert_(dest != nullptr); + for (int y = 0; y < region.h; y++) { + memcpy(dest + (size_t)dstStride * y, region.data + (size_t)srcStride * y, dstStride); + } + vkTex_->CopyBufferToMipLevelRegion(cmd, &batch, level, region.x, region.y, region.w, region.h, 0, buf, offset, region.w); + } + vkTex_->FinishCopyBatch(cmd, &batch); + + vkTex_->RestoreAfterTransferDst(level + 1, postBarriers); +} + void VKTexture::UpdateInternal(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t * const *data, TextureCallback initDataCallback, int numLevels) { int w = width_; int h = height_; @@ -1401,6 +1434,23 @@ void VKContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, Text tex->Update(initCmd, &renderManager_.PostInitBarrier(), push_, data, initDataCallback, numLevels); } +void VKContext::UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) { + if (numRegions <= 0) { + return; + } + VkCommandBuffer initCmd = renderManager_.GetInitCmd(); + if (!push_ || !initCmd) { + // Too early! Fail. + ERROR_LOG(Log::G3D, "Can't update textures before the first frame has started."); + return; + } + + VKTexture *tex = (VKTexture *)texture; + + _dbg_assert_(level < tex->NumLevels()); + tex->UpdateRegions(initCmd, &renderManager_.PostInitBarrier(), push_, level, regions, numRegions); +} + static inline void CopySide(VkStencilOpState &dest, const StencilSetup &src) { dest.compareOp = compToVK[(int)src.compareOp]; dest.failOp = stencilOpToVK[(int)src.failOp]; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index a31c8e102e..75edf35f92 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -669,6 +669,18 @@ struct TextureDesc { TextureCallback initDataCallback; }; +// A rectangular piece of a single mip level, for DrawContext::UpdateTextureRegions. +struct TextureRegionUpdate { + int x; + int y; + int w; + int h; + // Points at the top left pixel of the region. Does not take ownership. + const uint8_t *data; + // Distance between rows in data, in bytes. 0 means tightly packed, that is w pixels. + int byteStride; +}; + enum class RPAction { KEEP = 0, CLEAR = 1, @@ -790,6 +802,10 @@ public: // while in Vulkan this might cause various strangeness like image corruption. virtual void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) = 0; + // Replaces a set of rectangular regions of a single mip level, leaving the rest of the texture alone. + // Same in-flight caveat as UpdateTextureLevels. The regions must not overlap each other. + virtual void UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) = 0; + virtual void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, Aspect aspects, const char *tag) = 0; virtual bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, Aspect aspects, FBBlitFilter filter, const char *tag) = 0; diff --git a/ext/imgui/imgui_impl_thin3d.cpp b/ext/imgui/imgui_impl_thin3d.cpp index 713b6423b3..95fd0d55b0 100644 --- a/ext/imgui/imgui_impl_thin3d.cpp +++ b/ext/imgui/imgui_impl_thin3d.cpp @@ -53,9 +53,9 @@ static BackendData *ImGui_ImplThin3d_GetBackendData() { } // Since 1.92 imgui owns its textures (the font atlas, and any more it needs as glyphs get -// rasterized on demand) and asks the backend to create, refresh and destroy them. thin3d can only -// replace a whole mip level, not a sub-rectangle, so an update re-uploads everything - which is -// fine, since these only change when a new glyph shows up. +// rasterized on demand) and asks the backend to create, refresh and destroy them. An update comes +// with a list of dirty rectangles, which we hand to thin3d as sub-rectangle uploads so a single new +// glyph doesn't cost a re-upload of the whole atlas. static void ImGui_ImplThin3d_UpdateTexture(Draw::DrawContext *draw, ImTextureData *tex) { BackendData *bd = ImGui_ImplThin3d_GetBackendData(); @@ -103,8 +103,21 @@ static void ImGui_ImplThin3d_UpdateTexture(Draw::DrawContext *draw, ImTextureDat ERROR_LOG(Log::System, "imgui: asked to update texture %d, which we don't have", (int)tex->GetTexID()); return; } - const uint8_t *data = (const uint8_t *)tex->GetPixels(); - draw->UpdateTextureLevels(bd->imguiTextures[index], &data, nullptr, 1); + std::vector regions; + regions.reserve(tex->Updates.Size); + for (const ImTextureRect &rect : tex->Updates) { + Draw::TextureRegionUpdate region; + region.x = rect.x; + region.y = rect.y; + region.w = rect.w; + region.h = rect.h; + region.data = (const uint8_t *)tex->GetPixelsAt(rect.x, rect.y); + region.byteStride = tex->GetPitch(); + regions.push_back(region); + } + if (!regions.empty()) { + draw->UpdateTextureRegions(bd->imguiTextures[index], 0, regions.data(), (int)regions.size()); + } tex->SetStatus(ImTextureStatus_OK); break; }