Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/merges/2026-08-24-libultraship.md
Original file line number Diff line number Diff line change
@@ -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)

<details><summary>new commits</summary>

```
62e973ae Unify the pyramidlike test (#1239)
```
</details>

**Conflict surface** (our customized files upstream touched):

```
src/fast/interpreter.cpp
```
64 changes: 27 additions & 37 deletions libultraship/src/fast/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,16 @@ static uint32_t GetTileSizeFromCoordinates(float low, float high) {
return static_cast<uint32_t>(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<uint32_t>(width / hs) * static_cast<uint32_t>(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 =
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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];
}

Expand Down
4 changes: 2 additions & 2 deletions upstream-pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading