Repository navigation
fix(render): follow-ups to upscaled textures: canvas pixels and size limit - #758
Merged
3 commits merged intoOct 17, 2026
Merged
3 commits merged into
3 commits merged into
Conversation
The game draws two textures through their CPU pixels: the guild mark (BITMAP_GUILD, CreateGuildMark) and the castle owner's mark on the flag in Valley of Loren (BITMAP_INTERFACE_MAP, CreateCastleMark). Only a comment kept upscaled art away from them, and it named the wrong ones. An upscaled Object31/Flag.OZT lost its pixels after the upload and the castle mark wrote through a null pointer. They now load through LoadCanvasBitmap, which asks the loader to keep the pixels. The mark writers are laid out for the original size, so an upscaled file loaded into a canvas is shrunk back to that size, each pixel the average of its block, and uploaded without mips. CreateCastleMark also checks that the bitmap is loaded, like CreateGuildMark.
Apart from the two canvases, nothing reads a texture's pixels after the upload, so the loader now frees them for every texture, originals included, not only for upscaled ones. Texture Memory in the debug console counted those CPU copies, so it went down when a pack was installed and would now read almost zero. It counts the base level of each uploaded texture instead.
MAX_WIDTH and MAX_HEIGHT (1024) were checked against the stored image before the marker was read, so a 4x file of any original over 256 px, or a 2x file of one over 512 px, did not load. The log blamed TurboJPEG or an invalid OZT format. The loader now reads the marker first and accepts a marked image up to its scale times the limit; the power-of-two padding uses the same bound. A rejected image logs its size and the limit.
7 of 8 tasks
Collaborator
|
This PR is merged unchanged into the integration PR #764, together with #622 and #716, whose conflicts with #718 and #758 are resolved there. When #764 merges with a merge commit, GitHub marks this PR as merged too. Please don't push to this branch until #764 merges. A new commit here would leave this PR open, and it would have to be merged into #764 again. What #764 does with this PR's changes:
I tested it in game with your pack: about 1,880 textures mipmapped, about 2 GB on the GPU, no load errors, in both Classic and Enhanced. |
This was referenced Oct 17, 2026
nitoygo
added a commit
that referenced
this pull request
Oct 17, 2026
nitoygo
added a commit
that referenced
this pull request
Oct 17, 2026
Resolved against the upscaled textures on main (#718) and #758: - The loader moved to GlobalBitmapLoad.cpp; it now carries #718/#758's marker scale, original-size Width/Height, premultiplied RmlUi images, canvases that keep their pixels, the released CPU copy and the base-level texture memory count. - The uploader keeps its three 4 MiB slots and stages an image larger than one slot (marked files up to 4x the 1024 px limit) in bands of rows, one submission each. - Mipmapped uploads: the GPU builds the chain after the last band, and two more shared samplers (trilinear, anisotropic, repeat/clamp) serve them, so upscaled textures no longer create a sampler each. - Tests: six shared samplers, the wider size limit, banded staging with mips, and a marked file in the loader (mipmapped, original size, released pixels; a canvas shrunk and kept).
nitoygo
added a commit
that referenced
this pull request
Oct 17, 2026
Resolved against main's renderer and the upscaled textures (#718, #758): - Skinned draws: Enhanced lighting (its pipelines, alpha-to-coverage, the world sampler, eye position, specular and world-space rows) moved into RecordSkinnedDraw, the path of the GPU mesh cache; the per-draw vertex upload of #716's old path is gone. A skinned draw's colour is one per-instance value, so it gives the draw's alpha. - Frame replay: #716's passes (frame, post-processed frame, continued flush) keep main's replay profiler scope and main's editor pass after RmlUi; #716's editor marker inside the replay is dropped. - The GPU body-shadow pipelines join the single-sample twins, so shadows still draw outside the multisampled main pass; they take no coverage. - Upload: #716's mip levels and the row bands share one staging loop; a texture can also have the GPU build its chain. The uploader is back to four shared samplers. - Textures: upscaled art is always mipmapped and sampled through #716's anisotropic samplers, in both looks. Opaque images get GPU-built mips, images with alpha #716's coverage-preserving CPU chain, at load and on the switch to Enhanced. The pixels are released after the upload, so that switch reads the file again. - Shader checks: both sides' shaders in the validation lists; the raw bone buffer check covers the skinned vertex shaders only. - Docs: the upload staging note, and Anisotropy applies to every mipmapped texture.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to PR #718, addressing the two follow-up points from the review.
Canvas textures keep their pixels (
fix(render): keep the pixels of canvas textures)Two textures are drawn on through their CPU pixels:
BITMAP_GUILD(CreateGuildMark) andBITMAP_INTERFACE_MAP(Object31\Flag.tga,CreateCastleMark). They now load through a newLoadCanvasBitmap, which asks the loader to keepBuffer.CreateCastleMarkgets the same null check asCreateGuildMark, and the comments name the right textures.One deviation from the suggestion: the loader doesn't ignore the marker for a canvas, it shrinks the image back to the original size (each pixel is the average of its block) and uploads it without mips. Ignoring the marker would load
guild.OZTat 4x as 32x32 instead of 8x8, andCreateGuildMarkwould then read 1024 bytes out of a 64-byte mark. Rejecting the file isn't an option either, because a failedLoadBitmapcloses the game. With the shrink, a pack can include these files and nothing breaks.Bufferis released for every texture (perf(render): release the CPU copy of every texture after upload)Apart from the two canvases nothing reads
Bufferafter the upload (checked with grep oversrc/source), so it's freed for originals too. "Texture Memory" in the debug console counted those CPU copies; it now counts the base level of each uploaded texture, so it no longer drops when a pack is installed.Size limit scales with the marker (
fix(render): allow marked textures up to their scale times 1024 px)The marker is read before the size check, so a marked image is accepted up to
MAX_WIDTH * scalexMAX_HEIGHT * scale. The power-of-two padding uses the same bound (otherwise an image over 1024 px would be padded to less than its own size). A rejected image logs its size and the limit instead of a TurboJPEG error or "invalid format". Note that loading a 4096 px 4x image temporarily takes about 160 MB in the 32-bit client; the current pack has none.Testing
upscale_marker_testsandozt_file_testspass (17/17), including new tests for the shrink and for reading the marker's scale.main+ PR 718: world and UI textures, guild marks, Valley of Loren (castle flag loads; no castle owner, so the mark itself wasn't drawn), general play. Resident memory on the same map: 2,078,644 KB withmain+ PR 718, 659,548 KB with this PR on top (about 1.4 GB less). No commit between the twomainbases touches texture loading.