Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FF8: field texture replacement #542

Merged
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
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

- Full commit list since last stable release: https://github.com/julianxhokaxhiu/FFNx/compare/1.15.0...master

## Common

- Improve texture upload time by reducing allocations for color conversion

## FF7

- Input: Allow Cloud to walk/run based on the left analogue stick position ( https://github.com/julianxhokaxhiu/FFNx/issues/523 )

## FF8

- Common: Fix startup hang on launch
- Graphics: Add Field texture replacement ( https://github.com/julianxhokaxhiu/FFNx/pull/542 )

# 1.15.0

Expand Down
37 changes: 27 additions & 10 deletions src/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ bool simulate_OK_button = false;

GamepadAnalogueIntent gamepad_analogue_intent = INTENT_NONE;

uint32_t *image_data_cache = nullptr;
uint32_t image_data_size_cache = 0;

uint8_t *image_data_scaled_cache = nullptr;
uint32_t image_data_scaled_size_cache = 0;

uint32_t noop() { return 0; }
uint32_t noop_a1(uint32_t a1) { return 0; }
uint32_t noop_a2(uint32_t a1, uint32_t a2) { return 0; }
Expand Down Expand Up @@ -1506,7 +1512,16 @@ uint32_t load_external_texture(void* image_data, uint32_t dataSize, struct textu
if (scale > 1)
{
uint32_t image_data_size = originalWidth * scale * originalHeight * scale * 4;
image_data_scaled = (uint8_t*)driver_malloc(image_data_size);
// Allocate with cache
if (image_data_scaled_size_cache == 0 || image_data_size > image_data_scaled_size_cache) {
if (image_data_scaled_cache != nullptr) {
driver_free(image_data_scaled_cache);
}
image_data_scaled_cache = (uint8_t*)driver_malloc(image_data_size);
image_data_scaled_size_cache = image_data_size;
}

image_data_scaled = image_data_scaled_cache;

// convert source data
if (image_data_scaled != nullptr)
Expand Down Expand Up @@ -1535,11 +1550,6 @@ uint32_t load_external_texture(void* image_data, uint32_t dataSize, struct textu
VRASS(texture_set, ogl.external, false);
}

if (image_data_scaled != nullptr && image_data_scaled != image_data)
{
driver_free(image_data_scaled);
}

if (textureType == TexturePacker::InternalTexture)
{
gl_replace_texture(texture_set, VREF(tex_header, palette_index), texture);
Expand Down Expand Up @@ -1822,7 +1832,17 @@ struct texture_set *common_load_texture(struct texture_set *_texture_set, struct

// allocate PBO
uint32_t image_data_size = w * h * 4;
image_data = (uint32_t*)driver_malloc(image_data_size);

// Allocate with cache
if (image_data_size_cache == 0 || image_data_size > image_data_size_cache) {
if (image_data_cache != nullptr) {
driver_free(image_data_cache);
}
image_data_cache = (uint32_t*)driver_malloc(image_data_size);
image_data_size_cache = image_data_size;
}

image_data = image_data_cache;

// convert source data
if (image_data != NULL) convert_image_data(VREF(tex_header, image_data), image_data, w, h, tex_format, invert_alpha, color_key, palette_offset, reference_alpha);
Expand All @@ -1839,9 +1859,6 @@ struct texture_set *common_load_texture(struct texture_set *_texture_set, struct
// commit PBO and populate texture set
gl_upload_texture(_texture_set, VREF(tex_header, palette_index), image_data, RendererTextureType::BGRA);
}

// free the memory buffer
driver_free(image_data);
}
}
else ffnx_unexpected("no texture format specified or no source data\n");
Expand Down
66 changes: 66 additions & 0 deletions src/ff8.h
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,66 @@ struct ff8_gfx_driver
gfx_field_EC *field_EC;
};

struct ff8_field_state_common {
uint8_t stack_data[0x140];
uint32_t field_140;
uint32_t field_144;
uint32_t field_148;
uint32_t field_14c;
uint32_t field_150;
uint32_t field_154;
uint32_t field_158;
uint32_t field_15c;
uint32_t execution_flags; // bgdraw: 0x10, bganime/rbganime: 0x980 (0x800: animation ongoing)
uint32_t field_164;
uint32_t field_168;
uint32_t field_16c;
uint32_t field_170;
uint8_t field_174; // has anim?
uint8_t field_175; // has anim mask?
uint16_t current_instruction_position; // field_176
uint32_t field_178;
uint32_t field_17c;
uint32_t field_180;
uint8_t stack_current_position; // field_184
uint8_t field_185;
uint8_t field_186;
uint8_t field_187;
};

struct ff8_field_state_background {
ff8_field_state_common common;
uint16_t bgstate; // field_188, set to -1 if off
uint16_t field_18a;
uint16_t bgparam_anim_start; // field_18c
uint16_t bgparam_anim_end; // field_18e
uint16_t bgparam_anim_speed1; // field_190
uint16_t bgparam_anim_speed2; // field_192
uint16_t bgparam_anim_flags; // field_194
uint16_t field_196;
uint32_t field_198;
uint16_t bgshadeloop_remember_stack_pointer; // field_19c
uint16_t bgshade_add_value; // field_19e
uint16_t field_1a0; // bgshadeloop
uint16_t field_1a2; // bgshadeloop
uint8_t field_1a4; // bgshadeloop
uint8_t field_1a5; // bgshadeloop
uint8_t field_1a6; // bgshadeloop
uint8_t bgshade_color1r; // field_1a7
uint8_t bgshade_color1g; // field_1a8
uint8_t bgshade_color1b; // field_1a9
uint8_t bgshade_color2r; // field_1aa
uint8_t bgshade_color2g; // field_1ab
uint8_t bgshade_color2b; // field_1ac
uint8_t bgshade_color1r_2; // field_1ad
uint8_t bgshade_color1g_2; // field_1ae
uint8_t bgshade_color1b_2; // field_1af
uint8_t bgshade_color2r_2; // field_1b0
uint8_t bgshade_color2g_2; // field_1b1
uint8_t bgshade_color2b_2; // field_1b2
uint8_t field_1b3;
};

// --------------- end of FF8 imports ---------------

// memory addresses and function pointers from FF8.exe
Expand Down Expand Up @@ -986,7 +1046,13 @@ struct ff8_externals
uint32_t read_field_data;
uint32_t upload_mim_file;
char *field_filename;
uint32_t field_scripts_init;
uint8_t *field_state_background_count;
ff8_field_state_background **field_state_backgrounds;
uint32_t load_field_models;
uint32_t chara_one_read_file;
uint32_t chara_one_seek_file;
uint32_t chara_one_upload_texture;
uint32_t worldmap_main_loop;
uint32_t worldmap_enter_main;
uint32_t worldmap_sub_53F310;
Expand Down
144 changes: 144 additions & 0 deletions src/ff8/field/background.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/****************************************************************************/
// Copyright (C) 2009 Aali132 //
// Copyright (C) 2018 quantumpencil //
// Copyright (C) 2018 Maxime Bacoux //
// Copyright (C) 2020 Chris Rizzitello //
// Copyright (C) 2020 John Pritchard //
// Copyright (C) 2023 myst6re //
// Copyright (C) 2023 Julian Xhokaxhiu //
// Copyright (C) 2023 Cosmos //
// Copyright (C) 2023 Tang-Tang Zhou //
// //
// This file is part of FFNx //
// //
// FFNx is free software: you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation, either version 3 of the License //
// //
// FFNx is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details. //
/****************************************************************************/

#include "background.h"
#include "../../image/tim.h"
#include "../../saveload.h"
#include "../../log.h"

bool ff8_background_tiles_looks_alike(const Tile &tile, const Tile &other)
{
return tile.texID == other.texID
&& tile.palID == other.palID
&& tile.srcX == other.srcX
&& tile.srcY == other.srcY
&& tile.blendType == other.blendType;
}

std::vector<Tile> ff8_background_parse_tiles(const uint8_t *map_data)
{
std::vector<Tile> tiles;

while (true) {
Tile tile;

memcpy(&tile, map_data, sizeof(Tile));

if (tile.x == 0x7fff) {
break;
}

uint8_t texture_id = tile.texID & 0xF;
Tim::Bpp bpp = Tim::Bpp((tile.texID >> 7) & 3);
uint8_t pal_id = (tile.palID >> 6) & 0xF;

if (trace_all || trace_vram) ffnx_info("tile %d dst %d %d %d src %d %d texid %d bpp %d palId %d blendType %d param %d %d\n", tiles.size(), tile.x, tile.y, tile.z, tile.srcX, tile.srcY, texture_id, int(bpp), pal_id, tile.blendType, tile.parameter, tile.state);

tiles.push_back(tile);

map_data += sizeof(Tile);
}

return tiles;
}

bool ff8_background_save_textures(const std::vector<Tile> &tiles, const uint8_t *mim_data, const char *filename)
{
if (trace_all || trace_vram) ffnx_trace("%s %s\n", __func__, filename);

const uint16_t* const palettes_data = reinterpret_cast<const uint16_t *>(mim_data + 0x1000);
const uint8_t* const textures_data = mim_data + 0x3000;

const uint8_t cols_count = tiles.size() / (TEXTURE_HEIGHT / TILE_SIZE) + int(tiles.size() % (TEXTURE_HEIGHT / TILE_SIZE) != 0);
const uint16_t width = cols_count * TILE_SIZE;
const uint32_t image_data_size = width * TEXTURE_HEIGHT * sizeof(uint32_t);

uint32_t* const image_data_start = new uint32_t[width * TEXTURE_HEIGHT];

if (image_data_start == nullptr) {
return false;
}

// Fill with zeroes (transparent image)
memset(image_data_start, 0, image_data_size);

uint32_t tile_id = 0;

for (const Tile &tile: tiles) {
Tim::Bpp bpp = Tim::Bpp((tile.texID >> 7) & 3);
uint8_t texture_id = tile.texID & 0xF;
uint8_t pal_id = (tile.palID >> 6) & 0xF;
const uint8_t *texture_data_start = textures_data + texture_id * TEXTURE_WIDTH_BYTES + tile.srcY * MIM_DATA_WIDTH_BYTES;
const uint16_t *palette_data_start = bpp == Tim::Bpp16 ? nullptr : palettes_data + pal_id * PALETTE_SIZE;
uint8_t row = tile_id / cols_count, col = tile_id % cols_count;
uint32_t *target = image_data_start + row * TILE_SIZE * width;

if (bpp == Tim::Bpp16) {
const uint16_t *texture_data = reinterpret_cast<const uint16_t *>(texture_data_start) + tile.srcX;
target += col * TILE_SIZE;

for (int y = 0; y < TILE_SIZE; ++y) {
for (int x = 0; x < TILE_SIZE; ++x) {
*(target + x) = fromR5G5B5Color(*(texture_data + x), true);
}

target += width;
texture_data += MIM_DATA_WIDTH_BYTES / 2;
}
} else if (bpp == Tim::Bpp8) {
const uint8_t *texture_data = texture_data_start + tile.srcX;
target += col * TILE_SIZE;

for (int y = 0; y < TILE_SIZE; ++y) {
for (int x = 0; x < TILE_SIZE; ++x) {
*(target + x) = fromR5G5B5Color(palette_data_start[*(texture_data + x)], true);
}

target += width;
texture_data += MIM_DATA_WIDTH_BYTES;
}
} else {
const uint8_t *texture_data = texture_data_start + tile.srcX / 2;
target += col * TILE_SIZE;

for (int y = 0; y < TILE_SIZE; ++y) {
for (int x = 0; x < TILE_SIZE / 2; ++x) {
uint8_t index = *(texture_data + x);
*(target + x * 2) = fromR5G5B5Color(palette_data_start[index & 0xF], true);
*(target + x * 2 + 1) = fromR5G5B5Color(palette_data_start[index >> 4], true);
}

target += width;
texture_data += MIM_DATA_WIDTH_BYTES;
}
}

++tile_id;
}

save_texture(image_data_start, image_data_size, width, TEXTURE_HEIGHT, uint32_t(-1), filename, false);

delete[] image_data_start;

return true;
}
55 changes: 55 additions & 0 deletions src/ff8/field/background.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/****************************************************************************/
// Copyright (C) 2009 Aali132 //
// Copyright (C) 2018 quantumpencil //
// Copyright (C) 2018 Maxime Bacoux //
// Copyright (C) 2020 Chris Rizzitello //
// Copyright (C) 2020 John Pritchard //
// Copyright (C) 2023 myst6re //
// Copyright (C) 2023 Julian Xhokaxhiu //
// Copyright (C) 2023 Cosmos //
// Copyright (C) 2023 Tang-Tang Zhou //
// //
// This file is part of FFNx //
// //
// FFNx is free software: you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation, either version 3 of the License //
// //
// FFNx is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details. //
/****************************************************************************/

#pragma once

#include "../../common.h"

#include <vector>

constexpr int TEXTURE_WIDTH_BYTES = 128; // Real texture width depends on the texture depth (bpp4 => 256, bpp8 => 128, bpp16 => 64)
constexpr int TEXTURE_WIDTH_BPP16 = 64;
constexpr int TEXTURE_WIDTH_BPP8 = 128;
constexpr int TEXTURE_WIDTH_BPP4 = 256;
constexpr int TEXTURE_HEIGHT = 256;
constexpr int VRAM_PAGE_MIM_MAX_COUNT = 13;
constexpr int MIM_DATA_WIDTH_BYTES = TEXTURE_WIDTH_BYTES * VRAM_PAGE_MIM_MAX_COUNT;
constexpr int MIM_DATA_HEIGHT = TEXTURE_HEIGHT;
constexpr int TILE_SIZE = 16;
constexpr int PALETTE_SIZE = 256;

struct Tile {
int16_t x, y, z;
uint16_t texID; // 2 bits = depth | 2 bits = blend | 1 bit = draw | 4 bits = textureID
uint16_t palID; // 6 bits = Always 30 | 4 bits = PaletteID | 6 bits = Always 0
uint8_t srcX, srcY;
uint8_t layerID; // 0-7
uint8_t blendType; // 0-4
uint8_t parameter, state;
};

// A tile looks like another if it uses the same texture with the same palette and uses the same blending
bool ff8_background_tiles_looks_alike(const Tile &tile, const Tile &other);

std::vector<Tile> ff8_background_parse_tiles(const uint8_t *map_data);
bool ff8_background_save_textures(const std::vector<Tile> &tiles, const uint8_t *mim_data, const char *filename);
Loading