From c72962a73d3b925f9996be3ea53c04596c5dde69 Mon Sep 17 00:00:00 2001 From: saulo benigno Date: Fri, 2 Oct 2026 21:10:04 -0300 Subject: [PATCH] Add high-resolution UI controller buttons --- port/assets/buttons/a_butn__0.png | Bin 0 -> 16228 bytes port/assets/buttons/a_butn_sm__0.png | Bin 0 -> 12981 bytes port/assets/buttons/b_butn__0.png | Bin 0 -> 16901 bytes port/assets/buttons/b_butn_sm__0.png | Bin 0 -> 13580 bytes port/assets/buttons/buttons.json | 172 +++++++++++ port/assets/buttons/hud_msg_icons_sm__0.png | Bin 0 -> 13002 bytes port/assets/buttons/svg/a_butn.svg | 24 ++ port/assets/buttons/svg/b_butn.svg | 24 ++ port/assets/buttons/svg/x_butn.svg | 24 ++ port/assets/buttons/svg/y_butn.svg | 27 ++ port/assets/buttons/x_butn__0.png | Bin 0 -> 16447 bytes port/assets/buttons/y_butn__0.png | Bin 0 -> 18006 bytes port/linux/README.md | 4 +- port/linux/game/hud_hires_tags.c | 129 ++++++++- port/linux/src/hud_hires.c | 90 +++++- port/linux/src/hud_hires.h | 25 +- port/linux/src/xbox_textures.c | 11 + source/interface/ui_widget.c | 4 + tools/button_assets.py | 303 ++++++++++++++++++++ tools/embed_assets.py | 27 +- 20 files changed, 843 insertions(+), 21 deletions(-) create mode 100644 port/assets/buttons/a_butn__0.png create mode 100644 port/assets/buttons/a_butn_sm__0.png create mode 100644 port/assets/buttons/b_butn__0.png create mode 100644 port/assets/buttons/b_butn_sm__0.png create mode 100644 port/assets/buttons/buttons.json create mode 100644 port/assets/buttons/hud_msg_icons_sm__0.png create mode 100644 port/assets/buttons/svg/a_butn.svg create mode 100644 port/assets/buttons/svg/b_butn.svg create mode 100644 port/assets/buttons/svg/x_butn.svg create mode 100644 port/assets/buttons/svg/y_butn.svg create mode 100644 port/assets/buttons/x_butn__0.png create mode 100644 port/assets/buttons/y_butn__0.png create mode 100644 tools/button_assets.py diff --git a/port/assets/buttons/a_butn__0.png b/port/assets/buttons/a_butn__0.png new file mode 100644 index 0000000000000000000000000000000000000000..e39e8216fc128678f7dd81467a3dceb78060fa9f GIT binary patch literal 16228 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 gT^h_?I90%NT6#xJL literal 0 HcmV?d00001 diff --git a/port/assets/buttons/b_butn__0.png b/port/assets/buttons/b_butn__0.png new file mode 100644 index 0000000000000000000000000000000000000000..c486dd21b9e23d9bbbd6a91dab761aa05b785af0 GIT binary patch literal 16901 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 KT-G@yGywo+tPyVj literal 0 HcmV?d00001 diff --git a/port/assets/buttons/b_butn_sm__0.png b/port/assets/buttons/b_butn_sm__0.png new file mode 100644 index 0000000000000000000000000000000000000000..564975186958cf53a154b8aaa0282f9ab93c7ec7 GIT binary patch literal 13580 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 literal 0 HcmV?d00001 diff --git a/port/assets/buttons/svg/a_butn.svg b/port/assets/buttons/svg/a_butn.svg new file mode 100644 index 00000000..814f2f07 --- /dev/null +++ b/port/assets/buttons/svg/a_butn.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/buttons/svg/b_butn.svg b/port/assets/buttons/svg/b_butn.svg new file mode 100644 index 00000000..c6e5cf60 --- /dev/null +++ b/port/assets/buttons/svg/b_butn.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/buttons/svg/x_butn.svg b/port/assets/buttons/svg/x_butn.svg new file mode 100644 index 00000000..eed59f09 --- /dev/null +++ b/port/assets/buttons/svg/x_butn.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/buttons/svg/y_butn.svg b/port/assets/buttons/svg/y_butn.svg new file mode 100644 index 00000000..a64c0c78 --- /dev/null +++ b/port/assets/buttons/svg/y_butn.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/buttons/x_butn__0.png b/port/assets/buttons/x_butn__0.png new file mode 100644 index 0000000000000000000000000000000000000000..45d92c68f1a9b7fda5be90772153c2426734879c GIT binary patch literal 16447 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 literal 0 HcmV?d00001 diff --git a/port/linux/README.md b/port/linux/README.md index 505c5144..315146e6 100644 --- a/port/linux/README.md +++ b/port/linux/README.md @@ -155,7 +155,7 @@ the setting for one start of the game. It has priority over the file. | `display.interpolation` | `true` | `HALO_INTERPOLATION` | `true`: one frame for each refresh of the display. `false`: 30 frames each second, as on the Xbox. Refer to "Frame rate". | | `display.direct_camera` | `true` | `HALO_DIRECT_CAMERA` | `true`: in first person, on foot, the view points where the player aims in each frame, not where the last tick left it. Refer to "Frame rate". | | `display.high_res_hud` | `true` | `HALO_HIGH_RES_HUD` | `true`: the HUD (meters, counters, panels and their outlines, the motion sensor, reticles, waypoints, scopes) is drawn from the high-res assets in `port/assets/hud`, 8x the size of the maps' bitmaps. The bitmaps with English text keep the maps' own. `false`: the maps' own bitmaps. | -| `display.high_res_text` | `true` | `HALO_HIGH_RES_TEXT` | `true`: the menus' and HUD's text is drawn with the fonts in `port/assets/fonts` (Overpass, in place of the maps' Interstate) at the display's resolution, laid out as before, and the menus' titles are drawn from the high-res pictures in `port/assets/titles`. `false`: the maps' bitmap fonts and titles. | +| `display.high_res_text` | `true` | `HALO_HIGH_RES_TEXT` | `true`: the menus' and HUD's text is drawn with the fonts in `port/assets/fonts` (Overpass, in place of the maps' Interstate) at the display's resolution, laid out as before, the menus' titles are drawn from the high-res pictures in `port/assets/titles`, and the controller button icons from those in `port/assets/buttons`. `false`: the maps' bitmap fonts, titles and button icons. | | `display.player_names` | `"all"` | `HALO_PLAYER_NAMES` | In multiplayer, whose names are drawn above their heads: `"all"`, `"allies"`, `"enemies"` or `"none"`. An ally's name is drawn above the triangle the game shows over teammates. An enemy's name shows only while the enemy is in sight and not camouflaged, so it never shows where an enemy hides, and only as far away as the weapon in hand turns its reticle red over an enemy (at least 20 world units, the motion sensor's reach, and at most 70). | | `display.player_name_scale` | `1.0` | `HALO_PLAYER_NAME_SCALE` | How large the players' names are drawn: `1.0` is three quarters of the size of the HUD's text, from `0.25` to `4`. With high-res text, larger names are rasterized at their size, so they stay sharp. | | `display.scoreboard_team_layout` | `"teams"` | `HALO_SCOREBOARD_TEAM_LAYOUT` | How the multiplayer scoreboard (hold BACK, or F1) lists a team game's players. `"teams"`: a column for each team, red on the left and blue on the right. `"score"`: all the players in order of score. With more players than fit, the mouse wheel and Page Up / Page Down scroll the scoreboard. | @@ -478,7 +478,7 @@ The game sends the activity only to a Discord client of the same user. | Game code | All 466 C files of the game. The changes are in "Game source changes". | | Graphics | Direct3D 8 on OpenGL 4.5 core through SDL3 (`src/d3d8_gl.c`). The port translates the NV2A vertex shaders and register combiners to GLSL. It decodes all the Xbox texture formats. The vertex and index buffers come from a GL copy of the Xbox memory. | | High-res HUD | The HUD is drawn from high-res assets: redraws at 8x the size of the maps' bitmaps (4x for the largest), in `port/assets/hud`. They cover the meters, counters, panels and their outlines, the motion sensor, reticles, waypoints and scopes, but no bitmap with English text. `tools/hud_assets.py` makes them from the SVG redraws, and the build embeds them in the executable. When the game uploads one of those bitmaps, `src/hud_hires.c` gives the high-res texture in its place, if the bitmap's pixels are those of the English maps: another language's maps keep their own. The game sizes and places the HUD from its tags as before. `display.high_res_hud = false` turns this off. | -| High-res text | The menus' and HUD's text is drawn with Overpass (`port/assets/fonts`, SIL Open Font License) in place of the maps' bitmap fonts, which are Interstate. `src/text_hires.c` rasterizes each glyph with stb_truetype (`port/third_party/stb`) at the display's resolution, into an atlas that a placeholder bitmap of the game stands for. The game lays the text out from its font tags as before. The menus' titles (the screens' headers and the main menu's items) are pictures of text in the maps, so they are drawn as the high-res HUD is: `tools/title_assets.py` sets each one again in OpenCE, Roger White's public-domain Newtown respaced to match the maps' commercial title typeface (`tools/title_font.py`), at 4x the bitmap's size over its own plate or glow, each letter placed where the map's letter is, in `port/assets/titles`. The postgame carnage report's title is set over a hand-made SVG redraw of its panel (`port/assets/titles/svg`) instead. `display.high_res_text = false` turns it off. | +| High-res text | The menus' and HUD's text is drawn with Overpass (`port/assets/fonts`, SIL Open Font License) in place of the maps' bitmap fonts, which are Interstate. `src/text_hires.c` rasterizes each glyph with stb_truetype (`port/third_party/stb`) at the display's resolution, into an atlas that a placeholder bitmap of the game stands for. The game lays the text out from its font tags as before. The menus' titles (the screens' headers and the main menu's items) are pictures of text in the maps, so they are drawn as the high-res HUD is: `tools/title_assets.py` sets each one again in OpenCE, Roger White's public-domain Newtown respaced to match the maps' commercial title typeface (`tools/title_font.py`), at 4x the bitmap's size over its own plate or glow, each letter placed where the map's letter is, in `port/assets/titles`. The postgame carnage report's title is set over a hand-made SVG redraw of its panel (`port/assets/titles/svg`) instead. The A, B, X and Y button icons beside the menus' key labels and in their text ("Press A to Join") are drawn from hand-made SVG redraws at 8x the size of the maps' bitmaps (`port/assets/buttons`, made by `tools/button_assets.py`), in the same sizes and places. `display.high_res_text = false` turns it off. | | Sound | Xbox DirectSound on SDL3 audio (`src/dsound_sdl.c`): PCM and Xbox ADPCM, mixed at 48 kHz, with volume, pitch, mix bins, distance, stereo pan, occlusion and obstruction. There is no Doppler effect, no cones and no reverb. | | Input | XInput on SDL3 (`src/xinput_sdl.c`): keyboard, mouse, gamepads with rumble, and the debug keyboard for the console. | | Files | The Win32 file functions and the MSVC file functions on POSIX, with the translation of Xbox paths. | diff --git a/port/linux/game/hud_hires_tags.c b/port/linux/game/hud_hires_tags.c index 991e8a4f..7c7e8814 100644 --- a/port/linux/game/hud_hires_tags.c +++ b/port/linux/game/hud_hires_tags.c @@ -11,11 +11,20 @@ reused (when cache_block_index and base_address are cleared). The texture cache of the platform layer uploads the pixels at an address whenever they are written there, and asks hud_hires_asset_at which bitmap they are: a block reused for another bitmap, or a map unloaded, is a write, which asks again. + +A texture drawn for only some of a bitmap's sprites (hud_msg_icons_sm's +buttons) does not stand for its bitmap: the game draws those sprites from a +placeholder (hud_hires_sprite_bitmap), a bitmap of the same size and format, +so the same coordinates, made when first needed and deleted with the map; +the texture cache draws the texture for it (hud_hires_placeholder_texture). +When the texture cannot be drawn, the sprites are drawn from their bitmap. */ #include "cseries.h" #include "bitmaps/bitmap_group.h" #include "bitmaps/bitmap_group_lookup.h" +#include "bitmaps/bitmaps.h" +#include "rasterizer/xbox/rasterizer_xbox_hardware_bitmaps.h" #include "tag_files/tag_groups.h" /* the platform layer's (port/linux/src) */ @@ -24,16 +33,21 @@ long hud_hires_asset_count(void); char const *hud_hires_asset_tag(long asset); long hud_hires_asset_bitmap(long asset); long hud_hires_asset_fits(long asset, long width, long height); +unsigned long hud_hires_asset_sprites(long asset); +int hud_hires_sprites_drawable(long asset, unsigned long address, unsigned long level0_size); +void hud_hires_register_placeholder(long asset, unsigned long const *texture); void hud_hires_tags_loaded(void); void hud_hires_tags_unloaded(void); long hud_hires_asset_at(unsigned long address, long width, long height); +struct bitmap_data const *hud_hires_sprite_bitmap(struct bitmap_data const *bitmap, short sequence_index); /* ---------- constants */ enum { MAXIMUM_HIRES_BITMAPS = 128, + MAXIMUM_HIRES_SPRITE_BITMAPS = 8, }; /* ---------- globals */ @@ -45,6 +59,40 @@ static struct } hires_bitmaps[MAXIMUM_HIRES_BITMAPS]; static long hires_bitmap_count = 0; +/* the bitmaps some of whose sprites have a texture, with their placeholder, +and whether the texture can be drawn for the pixels at checked_address +(checked again when the texture cache loads them elsewhere) */ +static struct +{ + struct bitmap_data *bitmap; + long asset; + struct bitmap_data *placeholder; + boolean placeholder_failed; + void *checked_address; + boolean drawable; +} hires_sprite_bitmaps[MAXIMUM_HIRES_SPRITE_BITMAPS]; +static long hires_sprite_bitmap_count = 0; + +/* ---------- private code */ + +static void hud_hires_sprite_bitmaps_forget( + void) +{ + long index; + + for (index = 0; index < hires_sprite_bitmap_count; index++) + { + if (hires_sprite_bitmaps[index].placeholder) + { + hud_hires_register_placeholder(hires_sprite_bitmaps[index].asset, NULL); + bitmap_delete(hires_sprite_bitmaps[index].placeholder); + } + } + hires_sprite_bitmap_count = 0; + + return; +} + /* ---------- public code */ void hud_hires_tags_loaded( @@ -55,6 +103,7 @@ void hud_hires_tags_loaded( long missing = 0; hires_bitmap_count = 0; + hud_hires_sprite_bitmaps_forget(); for (asset = 0; asset < asset_count && hires_bitmap_count < MAXIMUM_HIRES_BITMAPS; asset++) { long group_index = tag_loaded(BITMAP_GROUP_TAG, hud_hires_asset_tag(asset)); @@ -71,6 +120,16 @@ void hud_hires_tags_loaded( platform_log("high-res hud: %s bitmap %ld is %dx%d here, which its texture does not fit", hud_hires_asset_tag(asset), hud_hires_asset_bitmap(asset), bitmap->width, bitmap->height); } + else if (hud_hires_asset_sprites(asset)) + { + if (hires_sprite_bitmap_count < MAXIMUM_HIRES_SPRITE_BITMAPS) + { + csmemset(&hires_sprite_bitmaps[hires_sprite_bitmap_count], 0, sizeof(hires_sprite_bitmaps[0])); + hires_sprite_bitmaps[hires_sprite_bitmap_count].bitmap = bitmap; + hires_sprite_bitmaps[hires_sprite_bitmap_count].asset = asset; + hires_sprite_bitmap_count++; + } + } else { hires_bitmaps[hires_bitmap_count].bitmap = bitmap; @@ -78,8 +137,8 @@ void hud_hires_tags_loaded( hires_bitmap_count++; } } - platform_log("high-res hud: %ld of %ld bitmaps in this map (%ld not in it)", - hires_bitmap_count, asset_count, missing); + platform_log("high-res hud: %ld of %ld bitmaps in this map, %ld for some of their sprites (%ld not in it)", + hires_bitmap_count + hires_sprite_bitmap_count, asset_count, hires_sprite_bitmap_count, missing); return; } @@ -88,10 +147,76 @@ void hud_hires_tags_unloaded( void) { hires_bitmap_count = 0; + hud_hires_sprite_bitmaps_forget(); return; } +struct bitmap_data const *hud_hires_sprite_bitmap( + struct bitmap_data const *bitmap, + short sequence_index) +{ + long index; + + for (index = 0; index < hires_sprite_bitmap_count; index++) + { + struct bitmap_data *sprite_bitmap = hires_sprite_bitmaps[index].bitmap; + + if (sprite_bitmap != bitmap || + sequence_index < 0 || + sequence_index >= 32 || + !(hud_hires_asset_sprites(hires_sprite_bitmaps[index].asset) & (1UL << sequence_index))) + { + continue; + } + /* (checked on its pixels in the texture cache, once they are loaded) */ + if (sprite_bitmap->cache_block_index == NONE || !sprite_bitmap->base_address) + break; + if (hires_sprite_bitmaps[index].checked_address != sprite_bitmap->base_address) + { + hires_sprite_bitmaps[index].checked_address = sprite_bitmap->base_address; + hires_sprite_bitmaps[index].drawable = hud_hires_sprites_drawable( + hires_sprite_bitmaps[index].asset, + (unsigned long)sprite_bitmap->base_address, + (unsigned long)bitmap_mipmap_get_pixel_data_size(sprite_bitmap, 0)) != 0; + } + if (hires_sprite_bitmaps[index].drawable && + !hires_sprite_bitmaps[index].placeholder && + !hires_sprite_bitmaps[index].placeholder_failed) + { + struct bitmap_data *placeholder = bitmap_2d_new( + sprite_bitmap->width, + sprite_bitmap->height, + 0, + sprite_bitmap->format); + + if (placeholder && !rasterizer_bitmap_new(placeholder)) + { + bitmap_delete(placeholder); + placeholder = NULL; + } + if (placeholder) + { + hud_hires_register_placeholder( + hires_sprite_bitmaps[index].asset, + (unsigned long const *)placeholder->hardware_format); + } + else + { + platform_log("high-res hud: no placeholder for %s's sprites: drawn as they are", + hud_hires_asset_tag(hires_sprite_bitmaps[index].asset)); + hires_sprite_bitmaps[index].placeholder_failed = TRUE; + } + hires_sprite_bitmaps[index].placeholder = placeholder; + } + if (hires_sprite_bitmaps[index].drawable && hires_sprite_bitmaps[index].placeholder) + return hires_sprite_bitmaps[index].placeholder; + break; + } + + return bitmap; +} + long hud_hires_asset_at( unsigned long address, long width, diff --git a/port/linux/src/hud_hires.c b/port/linux/src/hud_hires.c index d709207f..b4b2f491 100644 --- a/port/linux/src/hud_hires.c +++ b/port/linux/src/hud_hires.c @@ -9,13 +9,14 @@ port/linux/game/hud_hires_tags.c). Each texture is decoded from its PNG when first drawn and kept: up to 69 of the HUD's, about 225 MB with their mip levels, though a game draws only some (the scopes' only when zoomed), and the titles of the menus shown, about 3 MB each (11 MB for the carnage -report's, a whole panel). +report's, a whole panel), and their button icons, about 0.3 MiB each (5.3 +MiB for the message icons' sheet). They are drawn with linear filtering and their mip levels (d3d8_gl.c, configure_sampler), as they are larger than they appear. -The PNGs are the ones tools/hud_assets.py and title_assets.py write, so only -what they write is read: 8-bit RGBA, not interlaced, its data inflated with -the game's zlib. +The PNGs are the ones tools/hud_assets.py, title_assets.py and +button_assets.py write, so only what they write is read: 8-bit RGBA, not +interlaced, its data inflated with the game's zlib. */ #include "hud_hires.h" @@ -103,6 +104,87 @@ int hud_hires_override_coverage(long asset) return asset >= 0 && asset < hud_hires_asset_count() && hud_hires_embedded[asset].coverage; } +/* ---------- sprites */ + +/* the placeholders of the textures drawn for some of a bitmap's sprites +(port/linux/game/hud_hires_tags.c): their D3D textures' Data */ +#define MAXIMUM_PLACEHOLDERS 8 + +static struct +{ + unsigned long data; + long asset; +} placeholders[MAXIMUM_PLACEHOLDERS]; +static long placeholder_count = 0; + +/* the sequences whose sprites the texture is drawn for (hud_hires.h), or 0 */ +unsigned long hud_hires_asset_sprites(long asset) +{ + return hud_hires_embedded[asset].sprites; +} + +/* whether the texture drawn for some of a bitmap's sprites can be drawn for +the bitmap whose pixels are at address (guest virtual; its first mip level +level0_size bytes): its setting on, the pixels those it was drawn for, and +the texture decoded */ +int hud_hires_sprites_drawable(long asset, unsigned long address, unsigned long level0_size) +{ + static int hud_enabled = -1, titles_enabled = -1; + unsigned long levels; + + if (hud_enabled < 0) + { + hud_enabled = config_boolean("display.high_res_hud"); + titles_enabled = config_boolean("display.high_res_text"); + } + if (asset < 0 || asset >= hud_hires_asset_count() || !hud_hires_embedded[asset].sprites || + !(hud_hires_embedded[asset].title ? titles_enabled : hud_enabled)) + { + return 0; + } + if (crc32(0L, (const Bytef *)address, (uInt)level0_size) != hud_hires_embedded[asset].crc) + { + if (!textures[asset].other_pixels_logged) + { + platform_log("high-res hud: %s bitmap %d is not the one its sprites' texture was drawn for " + "here (another language's or a modified map): drawn as it is", + hud_hires_embedded[asset].tag, hud_hires_embedded[asset].bitmap); + textures[asset].other_pixels_logged = 1; + } + return 0; + } + return hud_hires_override_texture(asset, &levels) != 0; +} + +/* the placeholder the game draws the texture's sprites from: its D3D +texture (NULL forgets it) */ +void hud_hires_register_placeholder(long asset, const unsigned long *texture) +{ + long index; + + for (index = 0; index < placeholder_count && placeholders[index].asset != asset; index++) + ; + if (texture && index == placeholder_count && placeholder_count < MAXIMUM_PLACEHOLDERS) + placeholder_count++; + if (index < placeholder_count) + { + placeholders[index].data = texture ? texture[1] : 0; + placeholders[index].asset = asset; + } +} + +unsigned int hud_hires_placeholder_texture(unsigned long data, unsigned long *levels) +{ + long index; + + for (index = 0; data && index < placeholder_count; index++) + { + if (placeholders[index].data == data) + return hud_hires_override_texture(placeholders[index].asset, levels); + } + return 0; +} + /* ---------- decoding */ static unsigned long big_endian_long(const unsigned char *bytes) diff --git a/port/linux/src/hud_hires.h b/port/linux/src/hud_hires.h index 432260b8..28453a3c 100644 --- a/port/linux/src/hud_hires.h +++ b/port/linux/src/hud_hires.h @@ -5,11 +5,18 @@ The high-res HUD: textures drawn from the hand-made SVG redraws of the Halo PC HUD sheets (port/assets/hud, made by tools/hud_assets.py), each 8x the size of the bitmap of the Xbox maps it stands for (4x for the largest) and in that bitmap's layout. The menus' titles, pictures of text in the maps, are drawn -the same way (port/assets/titles, made by tools/title_assets.py). +the same way (port/assets/titles, made by tools/title_assets.py), and so are +their controller button icons (port/assets/buttons, made by +tools/button_assets.py). tools/embed_assets.py makes them C data (hud_hires_embedded); hud_hires.c decodes them, and the texture cache (xbox_textures.c) draws one in place of its bitmap whenever that bitmap's pixels are uploaded: the game still sizes and places the bitmap by its tag, so nothing else changes. +A texture may instead stand for only some of a bitmap's sprites (the +buttons among the message icons the menus set into their text): the game +draws those sprites from a placeholder bitmap of the same size +(port/linux/game/hud_hires_tags.c), which the texture cache draws the +texture for, and the bitmap's other sprites from the bitmap as before. */ #ifndef HUD_HIRES_H @@ -19,9 +26,12 @@ and places the bitmap by its tag, so nothing else changes. bitmap group tag's name, its index there, and the CRC-32 of its first mip level's pixels as the English maps have them); coverage: a meter's, whose green is how much of each texel its shapes cover (the meter shader reads -only its blue and alpha); title: a menu's title (port/assets/titles, made by -tools/title_assets.py), drawn with display.high_res_text rather than -display.high_res_hud */ +only its blue and alpha); title: the menus', a title (port/assets/titles, +made by tools/title_assets.py) or a button icon (port/assets/buttons, made +by tools/button_assets.py), drawn with display.high_res_text rather than +display.high_res_hud; sprites: the sequences of its bitmap's group (a bit +each) whose sprites it is drawn for, through a placeholder, or 0: the whole +bitmap, in its place */ struct hud_hires_embedded { const char *tag; @@ -30,6 +40,7 @@ struct hud_hires_embedded unsigned int crc; int coverage; int title; + unsigned int sprites; const unsigned int *png; unsigned int png_size; }; @@ -40,7 +51,7 @@ extern const unsigned int hud_hires_embedded_count; /* the texture standing for the bitmap whose pixels are uploaded from address (guest virtual) with this size, its first mip level being level0_size bytes, or -1: none, its setting (display.high_res_hud, or display.high_res_text for -a title) off, or pixels other than those the +the menus') off, or pixels other than those the texture was drawn for (another language's maps, which have their own text, or modified ones) */ long hud_hires_override_find(unsigned long address, unsigned long width, unsigned long height, @@ -50,5 +61,9 @@ could not be), and the number of its mip levels */ unsigned int hud_hires_override_texture(long asset, unsigned long *levels); /* whether its green is its coverage (d3d8_gl.c, nv2a_psh.c: coverage_alpha) */ int hud_hires_override_coverage(long asset); +/* the GL texture of the texture drawn for some of a bitmap's sprites whose +placeholder's D3D texture has this Data, and the number of its mip levels; +0 if data is no placeholder's */ +unsigned int hud_hires_placeholder_texture(unsigned long data, unsigned long *levels); #endif diff --git a/port/linux/src/xbox_textures.c b/port/linux/src/xbox_textures.c index 97fbf526..b17b2d47 100644 --- a/port/linux/src/xbox_textures.c +++ b/port/linux/src/xbox_textures.c @@ -714,6 +714,17 @@ static GLuint texture_entry_result(struct texture_entry *entry, GLenum *target, return atlas; } } + /* (a high-res texture drawn for some sprites, for the placeholder the + game draws them from: hud_hires.h) */ + { + GLuint texture = hud_hires_placeholder_texture(entry->data, &description->levels); + + if (texture) + { + description->hires = TRUE; + return texture; + } + } if (entry->override >= 0) { GLuint texture = hud_hires_override_texture(entry->override, &description->levels); diff --git a/source/interface/ui_widget.c b/source/interface/ui_widget.c index 6c0ba20b..d5fe7b87 100644 --- a/source/interface/ui_widget.c +++ b/source/interface/ui_widget.c @@ -3827,6 +3827,10 @@ static void render_state_bitmap( if (bitmap && _texture_cache_bitmap_get_hardware_format( (struct bitmap_data *)bitmap, FALSE, TRUE)) { + /* port: the sprite from a high-res texture, if it has one (port/linux/game/hud_hires_tags.c) */ + extern struct bitmap_data const *hud_hires_sprite_bitmap(struct bitmap_data const *sheet, short sequence_index); + + bitmap = hud_hires_sprite_bitmap(bitmap, icon->sequence_index); scale = hud_globals_get_scale(local_player_count() > 1); point.x = (short)(icon->offset.x * scale + cursor_bounds->x0 + 1.0f); point.y = (short)(cursor_bounds->y1 - icon->offset.y * scale - 2.0f); diff --git a/tools/button_assets.py b/tools/button_assets.py new file mode 100644 index 00000000..9d050e40 --- /dev/null +++ b/tools/button_assets.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +"""Makes high-res textures of the menus' controller button icons +(port/assets/buttons/*.png) from hand-drawn SVG redraws of them +(port/assets/buttons/svg): + + python tools/button_assets.py layout --map assets/maps/ui.map + python tools/button_assets.py build + python tools/button_assets.py check --map assets/maps/ui.map --out /tmp/button_check + +The icons beside the menus' key labels (A SELECT, B BACK, X DELETE, Y CREATE +NEW) are 32x32 bitmaps of ui.map with their art in the top left corner, +which the widgets' backgrounds draw at one texel to the pixel +(draw_bitmap_in_rect crops a bitmap larger than its widget, in coordinates +of the bitmap's size); the dialogs, the pause menu and the help screens draw +smaller A and B icons, bitmaps of their own in every map. So a texture of 8x +a bitmap's size, in its layout, draws in its place unchanged, as the +high-res HUD's do (port/linux/src/hud_hires.c), and with the high-res text +(display.high_res_text), as the menus' titles do. + +Each redraw is drawn in the texels (viewBox 0 0 32 32) of the large icon it +is named after, over the map's art: a tilted oval with a pale rim, dark at +the top and bright at the bottom, and its letter in white (Y's casting a +dark shadow). A small icon is the same art smaller. + +The menus' text draws the same buttons as message icons ("Press %a-button +to Join": draw_string_and_hack_in_icons, ui_widget.c), sprites of a sheet +of every map, hud_msg_icons_sm, that the game tints: stencils, each oval in +white with its letter cut out. They are drawn from the redraws' oval and +letter (their ids "oval" and "letter"), into a texture of the sheet's +layout that has only those sprites: the sheet's other icons (triggers and +sticks, with English words) keep the map's, so the texture stands for the +sprites alone, and the game draws them from it through a placeholder +(port/linux/game/hud_hires_tags.c). + +layout: reads each icon's bitmap from the map (its size, format and the CRC + of its pixels, which the game checks before drawing the texture in its + place) and places its redraw on it: a small icon's scaled and moved as + the map's small art is from its large art (so that their shapes' area + and centre are the same), and a message icon's stencil where it covers + the sprite's best. Writes port/assets/buttons/buttons.json. +build: renders the redraws as buttons.json places them into + port/assets/buttons/*.png (committed; the builds embed them: + tools/embed_assets.py). +check: compares each PNG, reduced to its bitmap's size, with the map's + bitmap and writes side-by-side images into --out. + +Needs rsvg-convert, Pillow, NumPy and SciPy. +""" + +import argparse +import json +import re +import sys +import tempfile +import xml.etree.ElementTree as ElementTree +import zlib +from pathlib import Path + +import numpy as np +from PIL import Image + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from hud_assets import (FORMATS, XboxMap, bleed, decode_bitmap, level0_size, overlap, # noqa: E402 + pixel_rectangle, render_svg) + +ROOT = Path(__file__).resolve().parent.parent +ASSETS = ROOT / "port/assets/buttons" +LIST = ASSETS / "buttons.json" +SCALE = 8 + +BITMAPS = "ui\\shell\\bitmaps\\" +# each icon's bitmap and its redraw (drawn over the bitmap it is named +# after): the button keys' (ui.map), and the smaller ones of the dialogs, +# the pause menu and the help screens (every map) +BUTTONS = { + BITMAPS + "a_butn": "a_butn.svg", + BITMAPS + "b_butn": "b_butn.svg", + BITMAPS + "x_butn": "x_butn.svg", + BITMAPS + "y_butn": "y_butn.svg", + BITMAPS + "a_butn_sm": "a_butn.svg", + BITMAPS + "b_butn_sm": "b_butn.svg", +} +# the message icons' sheet, and the redraw of each of its buttons, by the +# sequence of its sprite (on the sheet's first bitmap) +MESSAGE_ICONS = "ui\\hud\\bitmaps\\hud_msg_icons_sm" +MESSAGE_BUTTONS = {0: "a_butn.svg", 1: "b_butn.svg", 2: "x_butn.svg", 3: "y_butn.svg"} + + +def document(width: int, height: int, body: str) -> str: + return (f'\n{body}\n\n') + + +def placed(svg: Path, width: int, height: int, place: list) -> str: + """The redraw as a picture of its bitmap's size, scaled by place[0] and + moved by place[1:] (texels).""" + body = re.search(r"]*>(.*)", svg.read_text(), re.S).group(1) + scale, x, y = place + return document(width, height, f'{body}') + + +def stencil(svg: str, cell: list, place: list, index: int) -> str: + """A redraw as the message icons draw its button, in a sprite's cell + (texels of its sheet): its oval in white with its letter cut out, + scaled by place[0] and moved by place[1:] from the cell's corner, and + kept to the cell; index names its clip and mask.""" + root = ElementTree.parse(ASSETS / "svg" / svg).getroot() + parents = {child: parent for parent in root.iter() for child in parent} + oval, letter = root.find(".//*[@id='oval']"), root.find(".//*[@id='letter']") + left, top, right, bottom = cell + scale, x, y = place + moved = f"translate({left + x} {top + y}) scale({scale})" + area = f'x="{left}" y="{top}" width="{right - left}" height="{bottom - top}"' + return (f'\n' + f'\n' + f'\n' + f'' + f'') + + +def render(text: str, scale: int) -> np.ndarray: + """An SVG document at scale times its size.""" + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "placed.svg" + path.write_text(text) + return render_svg(path, scale) + + +def icon(xbox_map: XboxMap, tag: str) -> dict: + """An icon's bitmap (its group's only one).""" + if ("bitm", tag) not in xbox_map.tags: + sys.exit(f"{tag}: not in this map") + bitmaps = xbox_map.bitmap_group(tag)["bitmaps"] + if len(bitmaps) != 1: + sys.exit(f"{tag}: {len(bitmaps)} bitmaps, not the icon's one") + return bitmaps[0] + + +def shape(alpha: np.ndarray, scale: int = 1) -> tuple: + """The area and centre of a shape, in texels, from its alpha drawn at + scale times the texels.""" + alpha = alpha.astype(float) / 255 + ys, xs = np.mgrid[0:alpha.shape[0], 0:alpha.shape[1]] + area = alpha.sum() + return (area / scale ** 2, ((xs + 0.5) * alpha).sum() / area / scale, + ((ys + 0.5) * alpha).sum() / area / scale) + + +def stencil_place(svg: str, alpha: np.ndarray) -> list: + """Where a redraw's stencil goes in a sprite's cell, whose alpha it is: + where it covers it best, from where it has its area and centre.""" + from scipy import optimize + + height, width = alpha.shape + root = ElementTree.parse(ASSETS / "svg" / svg).getroot() + size = int(root.get("width")), int(root.get("height")) + area, x, y = shape(render(document(*size, stencil(svg, [0, 0, *size], [1, 0, 0], 0)), SCALE)[..., 3], SCALE) + sprite_area, sprite_x, sprite_y = shape(alpha) + scale = (sprite_area / area) ** 0.5 + target = alpha.astype(float) / 255 + + def error(place: np.ndarray) -> float: + drawn = render(document(width, height, stencil(svg, [0, 0, width, height], list(place), 0)), SCALE) + reduced = drawn[..., 3].astype(float).reshape(height, SCALE, width, SCALE).mean(axis=(1, 3)) / 255 + return float(((reduced - target) ** 2).sum()) + + best = optimize.minimize(error, [scale, sprite_x - scale * x, sprite_y - scale * y], method="Powell", + options={"xtol": 1e-3, "ftol": 1e-7}).x + return [round(float(best[0]), 4), round(float(best[1]), 3), round(float(best[2]), 3)] + + +def layout(arguments) -> None: + xbox_map = XboxMap(Path(arguments.map)) + entries = [] + for tag, svg in BUTTONS.items(): + bitmap = icon(xbox_map, tag) + width, height = bitmap["width"], bitmap["height"] + drawn_over = BITMAPS + Path(svg).stem + place = [1, 0, 0] + if drawn_over != tag: + area, x, y = shape(decode_bitmap(bitmap)[..., 3]) + drawn_area, drawn_x, drawn_y = shape(decode_bitmap(icon(xbox_map, drawn_over))[..., 3]) + scale = (area / drawn_area) ** 0.5 + place = [round(scale, 4), round(x - scale * drawn_x, 3), round(y - scale * drawn_y, 3)] + entries.append({ + "name": tag.split("\\")[-1] + "__0", + "tag": tag, + "bitmap": 0, + "width": width, + "height": height, + "format": FORMATS[bitmap["format"]], + "scale": SCALE, + # (the map's, which the game checks before drawing this in its + # place: other languages' maps and modified ones may differ) + "crc": zlib.crc32(bitmap["pixels"][:level0_size(bitmap)]), + "svg": svg, + "place": place, + }) + print(f"{tag}: {width}x{height} {FORMATS[bitmap['format']]}, {svg} at {place[0]}x, moved {place[1]}, {place[2]}") + # the message icons' buttons + if ("bitm", MESSAGE_ICONS) not in xbox_map.tags: + sys.exit(f"{MESSAGE_ICONS}: not in this map") + group = xbox_map.bitmap_group(MESSAGE_ICONS) + bitmap = group["bitmaps"][0] + width, height = bitmap["width"], bitmap["height"] + alpha = decode_bitmap(bitmap)[..., 3] + sprites = [] + for sequence, svg in MESSAGE_BUTTONS.items(): + cells = group["sequences"][sequence] + if len(cells) != 1 or cells[0][0] != 0: + sys.exit(f"{MESSAGE_ICONS}: sequence {sequence} is not one sprite of its first bitmap") + cell = pixel_rectangle(cells[0], width, height) + place = stencil_place(svg, alpha[cell[1]:cell[3], cell[0]:cell[2]]) + sprites.append({"sequence": sequence, "cell": cell, "svg": svg, "place": place}) + print(f"{MESSAGE_ICONS} sequence {sequence}: cell {cell}, {svg} at {place[0]}x, moved {place[1]}, {place[2]}") + entries.append({ + "name": MESSAGE_ICONS.split("\\")[-1] + "__0", + "tag": MESSAGE_ICONS, + "bitmap": 0, + "width": width, + "height": height, + "format": FORMATS[bitmap["format"]], + "scale": SCALE, + "crc": zlib.crc32(bitmap["pixels"][:level0_size(bitmap)]), + # (the sprites it stands for, not the whole bitmap) + "sprites": sprites, + }) + LIST.write_text(json.dumps({"assets": entries}, indent=1) + "\n") + + +def build(arguments) -> None: + description = json.loads(LIST.read_text()) + names = {f"{entry['name']}.png" for entry in description["assets"]} + for stale in ASSETS.glob("*.png"): + if stale.name not in names: + stale.unlink() + for entry in description["assets"]: + if "sprites" in entry: + body = "\n".join(stencil(sprite["svg"], sprite["cell"], sprite["place"], index) + for index, sprite in enumerate(entry["sprites"])) + image = render(document(entry["width"], entry["height"], body), entry["scale"]) + # (grey the same as alpha, as the sheet's) + image[..., :3] = image[..., 3:4] + else: + image = render(placed(ASSETS / "svg" / entry["svg"], entry["width"], entry["height"], entry["place"]), + entry["scale"]) + # (the colour carried into the transparent texels, so that + # filtering and the mip levels keep it at the oval's edge) + image = bleed(image) + Image.fromarray(image, "RGBA").save(ASSETS / f"{entry['name']}.png", optimize=True) + print(f"{entry['name']}.png: {image.shape[1]}x{image.shape[0]}") + + +def check(arguments) -> None: + xbox_map = XboxMap(Path(arguments.map)) + output = Path(arguments.out) + output.mkdir(parents=True, exist_ok=True) + for entry in json.loads(LIST.read_text())["assets"]: + if ("bitm", entry["tag"]) not in xbox_map.tags: + print(f"{entry['name']}: not in this map") + continue + bitmap = xbox_map.bitmap_group(entry["tag"])["bitmaps"][entry["bitmap"]] + if zlib.crc32(bitmap["pixels"][:level0_size(bitmap)]) != entry["crc"]: + print(f"{entry['name']}: this map's bitmap is not the one laid out") + xbox = decode_bitmap(bitmap) + image = Image.open(ASSETS / f"{entry['name']}.png").convert("RGBA") + reduced = np.asarray(image.resize((entry["width"], entry["height"]), Image.BOX)) + scale = entry["scale"] + # (each sprite's cell, or the whole bitmap) + cells = [sprite["cell"] for sprite in entry.get("sprites", [])] or [[0, 0, entry["width"], entry["height"]]] + for index, (left, top, right, bottom) in enumerate(cells): + score = overlap(reduced[top:bottom, left:right, 3].astype(float), xbox[top:bottom, left:right, 3].astype(float)) + name = entry["name"] + (f"__sprite{index}" if "sprites" in entry else "") + print(f"{name}: alpha overlap {score:.2f}") + # the map's | ours reduced | ours, over the menus' dark blue + ours = image.crop((left * scale, top * scale, right * scale, bottom * scale)) + panels = [Image.fromarray(np.ascontiguousarray(picture[top:bottom, left:right]), "RGBA").resize( + ours.size, Image.NEAREST) for picture in (xbox, reduced)] + panels.append(ours) + width, height = ours.size + sheet = Image.new("RGB", (width * 3 + 16, height), (255, 0, 0)) + for place, picture in enumerate(panels): + background = Image.new("RGBA", picture.size, (16, 26, 60, 255)) + sheet.paste(Image.alpha_composite(background, picture).convert("RGB"), (place * (width + 8), 0)) + sheet.save(output / f"{name}.png") + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + commands = parser.add_subparsers(dest="command", required=True) + command = commands.add_parser("layout") + command.add_argument("--map", required=True) + commands.add_parser("build") + command = commands.add_parser("check") + command.add_argument("--map", required=True) + command.add_argument("--out", required=True) + arguments = parser.parse_args() + {"layout": layout, "build": build, "check": check}[arguments.command](arguments) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/embed_assets.py b/tools/embed_assets.py index 4c3888bb..ac0f7980 100644 --- a/tools/embed_assets.py +++ b/tools/embed_assets.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 """Embeds the high-res HUD textures (port/assets/hud, made by tools/hud_assets.py), the menus' titles (port/assets/titles, made by -tools/title_assets.py) and the fonts the text is drawn with +tools/title_assets.py), their controller button icons (port/assets/buttons, +made by tools/button_assets.py) and the fonts the text is drawn with (port/assets/fonts) in the game as C data: python tools/embed_assets.py OUTPUT.c writes OUTPUT.c with each PNG and the bitmap it stands for (its tag, index -and the checksum of its pixels, from port/assets/hud/layout.json and -port/assets/titles/titles.json), as +and the checksum of its pixels, from port/assets/hud/layout.json, +port/assets/titles/titles.json and port/assets/buttons/buttons.json), as port/linux/src/hud_hires.h declares them. The builds generate it (hud_assets_build, called by tools/linux_build.py, windows_build.py and android_build.py), so the PNGs are the committed source and Android needs @@ -30,6 +31,8 @@ HUD_ASSETS = Path("port/assets/hud") LAYOUT = HUD_ASSETS / "layout.json" TITLE_ASSETS = Path("port/assets/titles") TITLE_LIST = TITLE_ASSETS / "titles.json" +BUTTON_ASSETS = Path("port/assets/buttons") +BUTTON_LIST = BUTTON_ASSETS / "buttons.json" FONT_ASSETS = Path("port/assets/fonts") FONT_LIST = FONT_ASSETS / "fonts.json" PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" @@ -45,9 +48,11 @@ def font_files() -> List[str]: def textures() -> List[tuple]: """The textures: each one's folder, its entry in its list, and whether - it is a title.""" + it is the menus' (a title or a button icon, drawn with the high-res + text).""" result = [] - for folder, listing, title in ((HUD_ASSETS, LAYOUT, False), (TITLE_ASSETS, TITLE_LIST, True)): + for folder, listing, title in ((HUD_ASSETS, LAYOUT, False), (TITLE_ASSETS, TITLE_LIST, True), + (BUTTON_ASSETS, BUTTON_LIST, True)): if (ROOT / listing).is_file(): result += [(folder, asset, title) for asset in json.loads((ROOT / listing).read_text())["assets"]] return result @@ -55,7 +60,7 @@ def textures() -> List[tuple]: def hud_asset_inputs() -> List[Path]: """The files the generated source is made from.""" - inputs = [listing for listing in (LAYOUT, TITLE_LIST, FONT_LIST) if (ROOT / listing).is_file()] + inputs = [listing for listing in (LAYOUT, TITLE_LIST, BUTTON_LIST, FONT_LIST) if (ROOT / listing).is_file()] if not inputs: return [] return [*inputs, *(folder / f"{asset['name']}.png" for folder, asset, _ in textures()), @@ -67,7 +72,8 @@ def hud_configure_inputs() -> List[Path]: folders, for files added or removed), not each file, which a change of a list may rename or remove.""" inputs = [] - for folder, listing in ((HUD_ASSETS, LAYOUT), (TITLE_ASSETS, TITLE_LIST), (FONT_ASSETS, FONT_LIST)): + for folder, listing in ((HUD_ASSETS, LAYOUT), (TITLE_ASSETS, TITLE_LIST), (BUTTON_ASSETS, BUTTON_LIST), + (FONT_ASSETS, FONT_LIST)): if (ROOT / listing).is_file(): inputs += [folder, listing] return inputs @@ -130,8 +136,13 @@ def main() -> None: lines.append("") tag = asset["tag"].replace("\\", "\\\\") coverage = int(any(cell["kind"] == "meter" for cell in asset.get("cells", []))) + # (the sequences whose sprites it stands for, a bit each of 32; 0: the whole bitmap) + sequences = [sprite["sequence"] for sprite in asset.get("sprites", [])] + if any(not 0 <= sequence < 32 for sequence in sequences): + sys.exit(f"{name}: sprite sequences must be 0 to 31 (hud_hires.h: sprites)") + sprites = sum(1 << sequence for sequence in sequences) table.append(f'\t{{ "{tag}", {asset["bitmap"]}, {width}, {height}, 0x{asset["crc"]:08x}u, {coverage}, ' - f'{int(title)}, asset{index}, {len(data)} }},') + f'{int(title)}, 0x{sprites:x}u, asset{index}, {len(data)} }},') lines.append("const struct hud_hires_embedded hud_hires_embedded[] =") lines.append("{") lines.extend(table)