diff --git a/docs/merges/2026-08-24-libultraship.md b/docs/merges/2026-08-24-libultraship.md new file mode 100644 index 000000000..04231e405 --- /dev/null +++ b/docs/merges/2026-08-24-libultraship.md @@ -0,0 +1,20 @@ +# Upstream merge — libultraship — 2026-08-24 + +> Auto-generated by `upstream-merge.yml`. Fill in the post-merge changes (build-fix +> chain, COMBO_BUILD guards, runtime fixes) before merging, then link this file from +> docs/UPSTREAM_MERGES.md. See that doc for the policy. + +### libultraship — `7cb10226e` → `62e973aeb` (1 merges) + +
new commits + +``` +62e973ae Unify the pyramidlike test (#1239) +``` +
+ +**Conflict surface** (our customized files upstream touched): + +``` +src/fast/interpreter.cpp +``` diff --git a/libultraship/src/fast/interpreter.cpp b/libultraship/src/fast/interpreter.cpp index 83e03e890..8394947f1 100644 --- a/libultraship/src/fast/interpreter.cpp +++ b/libultraship/src/fast/interpreter.cpp @@ -629,6 +629,16 @@ static uint32_t GetTileSizeFromCoordinates(float low, float high) { return static_cast(lroundf((high - low + 4.0f) / 4.0f)); } +// Loaded size is HD-scaled but the tile region is raw N64 texels; normalize before comparing. +static bool IsPyramidLike(uint32_t width, uint32_t height, uint32_t tileW, uint32_t tileH, float hByteScale, + float vPixelScale) { + const float hs = hByteScale > 0.0f ? hByteScale : 1.0f; + const float vs = vPixelScale > 0.0f ? vPixelScale : 1.0f; + const uint32_t loadedPixels = static_cast(width / hs) * static_cast(height / vs); + const uint32_t renderedPixels = tileW * tileH; + return renderedPixels > 0 && loadedPixels > renderedPixels && loadedPixels * 8 < renderedPixels * 13; // < 1.625x +} + void Interpreter::ImportTextureRgba16(int tile, bool importReplacement) { const RawTexMetadata* metadata = &mRdp->loaded_texture[mRdp->texture_tile[tile].tmem_index].raw_tex_metadata; const uint8_t* addr = @@ -656,10 +666,7 @@ void Interpreter::ImportTextureRgba16(int tile, bool importReplacement) { // skip both. CLAMP wrap mode always opts in. uint32_t tile_w = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].uls, mRdp->texture_tile[tile].lrs); uint32_t tile_h = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].ult, mRdp->texture_tile[tile].lrt); - uint32_t loadedPixels = width * height; - uint32_t renderedPixels = tile_w * tile_h; - bool pyramidLike = - renderedPixels > 0 && loadedPixels > renderedPixels && loadedPixels * 8 < renderedPixels * 13; // < 1.625x + bool pyramidLike = IsPyramidLike(width, height, tile_w, tile_h, metadata->h_byte_scale, metadata->v_pixel_scale); bool clampS = (mRdp->texture_tile[tile].cms & G_TX_CLAMP) != 0; bool clampT = (mRdp->texture_tile[tile].cmt & G_TX_CLAMP) != 0; // A masked axis wraps every 2^mask texels, so trim an over-loaded texture back to that. @@ -672,12 +679,10 @@ void Interpreter::ImportTextureRgba16(int tile, bool importReplacement) { if (maskH != 0 && (1u << maskH) >= tile_h && (1u << maskH) < height) { height = 1u << maskH; } - // HD replacement textures must still clamp to the rendered tile region - bool isHd = metadata->h_byte_scale != 1 || metadata->v_pixel_scale != 1; - if ((isHd || pyramidLike || clampS) && tile_w > 0 && tile_w < width) { + if ((pyramidLike || clampS) && tile_w > 0 && tile_w < width) { width = tile_w; } - if ((isHd || pyramidLike || clampT) && tile_h > 0 && tile_h < height) { + if ((pyramidLike || clampT) && tile_h > 0 && tile_h < height) { height = tile_h; } @@ -736,9 +741,7 @@ void Interpreter::ImportTextureRgba32(int tile, bool importReplacement) { // skip both. CLAMP wrap mode always opts in. uint32_t tile_w = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].uls, mRdp->texture_tile[tile].lrs); uint32_t tile_h = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].ult, mRdp->texture_tile[tile].lrt); - uint32_t loadedPixels = width * height; - uint32_t renderedPixels = tile_w * tile_h; - bool pyramidLike = renderedPixels > 0 && loadedPixels > renderedPixels && loadedPixels * 8 < renderedPixels * 13; + bool pyramidLike = IsPyramidLike(width, height, tile_w, tile_h, metadata->h_byte_scale, metadata->v_pixel_scale); bool clampS = (mRdp->texture_tile[tile].cms & G_TX_CLAMP) != 0; bool clampT = (mRdp->texture_tile[tile].cmt & G_TX_CLAMP) != 0; // A masked axis wraps every 2^mask texels, so trim an over-loaded texture back to that. @@ -751,12 +754,10 @@ void Interpreter::ImportTextureRgba32(int tile, bool importReplacement) { if (maskH != 0 && (1u << maskH) >= tile_h && (1u << maskH) < height) { height = 1u << maskH; } - // HD replacement textures must still clamp to the rendered tile region - bool isHd = metadata->h_byte_scale != 1 || metadata->v_pixel_scale != 1; - if ((isHd || pyramidLike || clampS) && tile_w > 0 && tile_w < width) { + if ((pyramidLike || clampS) && tile_w > 0 && tile_w < width) { width = tile_w; } - if ((isHd || pyramidLike || clampT) && tile_h > 0 && tile_h < height) { + if ((pyramidLike || clampT) && tile_h > 0 && tile_h < height) { height = tile_h; } @@ -1052,9 +1053,7 @@ void Interpreter::ImportTextureCi4(int tile, bool importReplacement) { // skip both. CLAMP wrap mode always opts in. uint32_t tile_w = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].uls, mRdp->texture_tile[tile].lrs); uint32_t tile_h = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].ult, mRdp->texture_tile[tile].lrt); - uint32_t loadedPixels = width * height; - uint32_t renderedPixels = tile_w * tile_h; - bool pyramidLike = renderedPixels > 0 && loadedPixels > renderedPixels && loadedPixels * 8 < renderedPixels * 13; + bool pyramidLike = IsPyramidLike(width, height, tile_w, tile_h, metadata->h_byte_scale, metadata->v_pixel_scale); bool clampS = (mRdp->texture_tile[tile].cms & G_TX_CLAMP) != 0; bool clampT = (mRdp->texture_tile[tile].cmt & G_TX_CLAMP) != 0; // A masked axis wraps every 2^mask texels, so trim an over-loaded texture back to that. @@ -1067,12 +1066,10 @@ void Interpreter::ImportTextureCi4(int tile, bool importReplacement) { if (maskH != 0 && (1u << maskH) >= tile_h && (1u << maskH) < height) { height = 1u << maskH; } - // HD replacement textures must still clamp to the rendered tile region - bool isHd = metadata->h_byte_scale != 1 || metadata->v_pixel_scale != 1; - if ((isHd || pyramidLike || clampS) && tile_w > 0 && tile_w < width) { + if ((pyramidLike || clampS) && tile_w > 0 && tile_w < width) { width = tile_w; } - if ((isHd || pyramidLike || clampT) && tile_h > 0 && tile_h < height) { + if ((pyramidLike || clampT) && tile_h > 0 && tile_h < height) { height = tile_h; } @@ -1156,9 +1153,7 @@ void Interpreter::ImportTextureCi8(int tile, bool importReplacement) { // skip both. CLAMP wrap mode always opts in. uint32_t tile_w = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].uls, mRdp->texture_tile[tile].lrs); uint32_t tile_h = GetTileSizeFromCoordinates(mRdp->texture_tile[tile].ult, mRdp->texture_tile[tile].lrt); - uint32_t loadedPixels = width * height; - uint32_t renderedPixels = tile_w * tile_h; - bool pyramidLike = renderedPixels > 0 && loadedPixels > renderedPixels && loadedPixels * 8 < renderedPixels * 13; + bool pyramidLike = IsPyramidLike(width, height, tile_w, tile_h, metadata->h_byte_scale, metadata->v_pixel_scale); bool clampS = (mRdp->texture_tile[tile].cms & G_TX_CLAMP) != 0; bool clampT = (mRdp->texture_tile[tile].cmt & G_TX_CLAMP) != 0; // A masked axis wraps every 2^mask texels, so trim an over-loaded texture back to that. @@ -1171,12 +1166,10 @@ void Interpreter::ImportTextureCi8(int tile, bool importReplacement) { if (maskH != 0 && (1u << maskH) >= tile_h && (1u << maskH) < height) { height = 1u << maskH; } - // HD replacement textures must still clamp to the rendered tile region - bool isHd = metadata->h_byte_scale != 1 || metadata->v_pixel_scale != 1; - if ((isHd || pyramidLike || clampS) && tile_w > 0 && tile_w < width) { + if ((pyramidLike || clampS) && tile_w > 0 && tile_w < width) { width = tile_w; } - if ((isHd || pyramidLike || clampT) && tile_h > 0 && tile_h < height) { + if ((pyramidLike || clampT) && tile_h > 0 && tile_h < height) { height = tile_h; } @@ -2061,9 +2054,9 @@ void Interpreter::GfxSpTri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx // Same pyramid-like ratio gate as ImportTexture: only clamp when loaded pixels // are close to rendered pixels (mipmap), not when much bigger (window scroll). - uint32_t loadedPx = tex_width[i] * tex_height[i]; - uint32_t renderedPx = tex_width2[i] * tex_height2[i]; - bool pyrLike = renderedPx > 0 && loadedPx > renderedPx && loadedPx * 8 < renderedPx * 13; + const RawTexMetadata& triMeta = mRdp->loaded_texture[mRdp->texture_tile[tile].tmem_index].raw_tex_metadata; + bool pyrLike = IsPyramidLike(tex_width[i], tex_height[i], tex_width2[i], tex_height2[i], + triMeta.h_byte_scale, triMeta.v_pixel_scale); // Same wrap-period trim as the import paths. The >= tex_width2 guard skips a stale // mask left by an FB blit (the pause background), which would otherwise tile the FB. uint32_t maskW = mRdp->texture_tile[tile].masks; @@ -2074,13 +2067,10 @@ void Interpreter::GfxSpTri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx if (maskH != 0 && (1u << maskH) >= tex_height2[i] && (1u << maskH) < tex_height[i]) { tex_height[i] = 1u << maskH; } - // HD replacements must clamp to the tile region - bool isHd = mRdp->loaded_texture[i].raw_tex_metadata.h_byte_scale != 1 || - mRdp->loaded_texture[i].raw_tex_metadata.v_pixel_scale != 1; - if ((isHd || pyrLike || (cms & G_TX_CLAMP)) && tex_width2[i] > 0 && tex_width2[i] < tex_width[i]) { + if ((pyrLike || (cms & G_TX_CLAMP)) && tex_width2[i] > 0 && tex_width2[i] < tex_width[i]) { tex_width[i] = tex_width2[i]; } - if ((isHd || pyrLike || (cmt & G_TX_CLAMP)) && tex_height2[i] > 0 && tex_height2[i] < tex_height[i]) { + if ((pyrLike || (cmt & G_TX_CLAMP)) && tex_height2[i] > 0 && tex_height2[i] < tex_height[i]) { tex_height[i] = tex_height2[i]; } diff --git a/upstream-pins.json b/upstream-pins.json index e6e1809b1..26615102d 100644 --- a/upstream-pins.json +++ b/upstream-pins.json @@ -7,8 +7,8 @@ "branch": "port-maintenance", "prefix": "libultraship", "subtree": "", - "mergedSha": "7cb10226e", - "mergedDate": "2026-08-19" + "mergedSha": "62e973aeb", + "mergedDate": "2026-08-24" }, "soh": { "remote": "https://github.com/HarbourMasters/Shipwright.git",