From 25fef821b066755b54ec139e00441cf0d2f5c24c Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sun, 10 Dec 2023 00:41:16 +0800 Subject: [PATCH] maple: read controller input on real hardware There were two notable bugs: - the maple transfer/data sizes were not being set correctly - align_32byte always realigned the address of `_scene`, and not the `mem` parameter as expected. This had the effect of the maple-DMA send and receive buffers being the same buffer. On real hardware, this causes unpredicable behavior. --- imask.h | 20 +++ main.cpp | 74 +++++++++-- maple/maple.cpp | 42 +++++-- maple/maple.h | 2 + maple/maple_bus_commands.h | 37 +++--- maple/maple_bus_ft0.h | 34 +++++ maple_bus_commands.h | 225 ---------------------------------- regs/gen/maple_commands.py | 6 +- regs/gen/maple_data_format.py | 124 +++++++++++++++++++ regs/maple_bus_commands.csv | 3 +- regs/maple_bus_commands.ods | Bin 17203 -> 17298 bytes regs/maple_bus_ft0.csv | 9 ++ regs/maple_bus_ft0.ods | Bin 11666 -> 11750 bytes scene.cpp | 4 +- scene.h | 2 +- 15 files changed, 308 insertions(+), 274 deletions(-) create mode 100644 imask.h create mode 100644 maple/maple_bus_ft0.h delete mode 100644 maple_bus_commands.h create mode 100644 regs/gen/maple_data_format.py create mode 100644 regs/maple_bus_ft0.csv diff --git a/imask.h b/imask.h new file mode 100644 index 0000000..620dcc0 --- /dev/null +++ b/imask.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +inline void set_imask(uint32_t imask) +{ + uint32_t sr; + + asm volatile ("stc sr,%0" + : "=r" (sr) + : + ); + + sr = (sr & ~0xf0) | (imask << 4); + + asm volatile ("ldc %0,sr" + : + : "r" (sr) + ); +} diff --git a/main.cpp b/main.cpp index 7071c90..a2af41a 100644 --- a/main.cpp +++ b/main.cpp @@ -11,6 +11,8 @@ #include "systembus.h" #include "maple/maple.h" #include "maple/maple_bits.h" +#include "maple/maple_bus_commands.h" +#include "maple/maple_bus_ft0.h" #include "holly/texture_memory_alloc.h" @@ -47,6 +49,10 @@ inline void serial_char(const char c) // wait for transmit fifo to become empty while ((sh7091.SCIF.SCFSR2 & SCFSR2__TDFE) == 0); + for (int i = 0; i < 100000; i++) { + asm volatile ("nop;"); + } + sh7091.SCIF.SCFTDR2 = static_cast(c); } @@ -60,14 +66,16 @@ void serial_string(const char * s) /* must be aligned to 32-bytes for DMA transfer */ // the aligned(32) attribute does not actually align to 32 bytes; gcc is the best compiler. // `+ 32` to allow for repositioning _scene to an actual 32-byte alignment. -uint32_t __attribute__((aligned(32))) _scene[((32 * 6) + 32) / 4]; +// __attribute__((aligned(32))) +uint32_t _scene[((32 * 6) + 32) / 4]; -uint32_t * align_32byte(uint32_t * mem) +template +T * align_32byte(T * mem) { - return reinterpret_cast(((reinterpret_cast(_scene) + 31) & ~31)); + return reinterpret_cast((((reinterpret_cast(mem) + 31) & ~31))); } -void serial_int(const uint32_t n) +void serial_int32(const uint32_t n) { char num_buf[9]; string::hex(num_buf, 8, n); @@ -77,19 +85,52 @@ void serial_int(const uint32_t n) serial_string("\n"); } -void maple_test() +void serial_int8(const uint8_t n) +{ + char num_buf[3]; + string::hex(num_buf, 2, n); + num_buf[2] = 0; + serial_string("0x"); + serial_string(num_buf); + serial_string("\n"); +} + +uint32_t _receive_address[(32 + 32) / 4] = {0}; +uint32_t _command_buf[(32 + 32) / 4] = {0}; + +bool maple_test() { - uint32_t _command_buf[(32 + 32) / 4]; - uint32_t _receive_address[(32 + 32) / 4]; uint32_t * command_buf = align_32byte(_command_buf); uint32_t * receive_address = align_32byte(_receive_address); + if ((((uint32_t)command_buf) & 31) != 0) serial_string("misaligned\n"); + if ((((uint32_t)receive_address) & 31) != 0) serial_string("misaligned\n"); + + for (int i = 0; i < (32 / 4); i++) { + command_buf[i] = 0; + } + + for (int i = 0; i < (32 / 4); i++) { + receive_address[i] = 0; + } + + v_sync_out(); + + //maple_init_device_request(command_buf, receive_address); + maple_init_get_condition(command_buf, receive_address); - maple_init_host_command(command_buf, receive_address); maple_dma_start(command_buf); - for (int i = 0; i < 32; i++) { - serial_int(receive_address[i]); + v_sync_in(); + + /* + for (int i = 0; i < (4 + 4 + 8); i++) { + serial_int8(reinterpret_cast(receive_address)[i]); } + */ + + // the data format for a FT0 (controller) data read + auto data_format = reinterpret_cast *>(&receive_address[1]); + return !(data_format->data.digital_button & ft0::data_transfer::digital_button::a); } extern "C" @@ -110,8 +151,7 @@ void main() v_sync_in(); - maple_test(); - + /* volatile uint16_t * framebuffer = reinterpret_cast(&texture_memory[0]); for (int y = 0; y < 480; y++) { for (int x = 0; x < 640; x++) { @@ -120,7 +160,9 @@ void main() framebuffer[y * 640 + x] = ((rgb.r >> 3) << 11) | ((rgb.g >> 2) << 5) | ((rgb.b >> 3) << 0); } } + */ + /* volatile texture_memory_alloc * mem = reinterpret_cast(0xa400'0000); volatile uint8_t * macaw = reinterpret_cast(&_binary_macaw_data_start); @@ -133,6 +175,7 @@ void main() uint16_t rgb565 = ((r / 8) << 11) | ((g / 4) << 5) | ((b / 8) << 0); mem->texture[px] = rgb565; } + */ holly.SOFTRESET = softreset::pipeline_soft_reset | softreset::ta_soft_reset; @@ -156,17 +199,22 @@ void main() } int frame = 0; + bool a_pressed = 0; + uint32_t color; while (true) { v_sync_out(); v_sync_in(); ta_polygon_converter_init(); - uint32_t ta_parameter_size = scene_transform(scene); + if (a_pressed) { color = 0xffffffff; } else { color = 0xffff7f00; } + uint32_t ta_parameter_size = scene_transform_quad(scene, color); ta_polygon_converter_transfer(scene, ta_parameter_size); ta_wait_opaque_list(); core_start_render(frame); + a_pressed = maple_test(); + frame = !frame; } } diff --git a/maple/maple.cpp b/maple/maple.cpp index 022d732..7bf3259 100644 --- a/maple/maple.cpp +++ b/maple/maple.cpp @@ -24,20 +24,41 @@ struct maple_host_command { } bus_data; }; -void maple_init_host_command(uint32_t * buf, uint32_t * receive_address) +void maple_init_host_command(uint32_t * buf, uint32_t * receive_address, uint8_t command_code, uint8_t data_size) { - auto host_command = reinterpret_cast *>(buf); + // this function does not care about the template instantiation of + // maple_host_command--data_fields is not manipulated here. + auto host_command = reinterpret_cast *>(buf); host_command->host_instruction = host_instruction::end_flag | host_instruction::port_select::a - | host_instruction::transfer_length(0); // 4 bytes + | host_instruction::transfer_length((data_size / 4)); - host_command->receive_data_storage_address = reinterpret_cast(receive_address); + host_command->receive_data_storage_address = reinterpret_cast(receive_address) & 0x1fff'ffff; - host_command->bus_data.command_code = device_request::command_code; + host_command->bus_data.command_code = command_code; host_command->bus_data.destination_ap = ap::de::device | ap::port_select::a; host_command->bus_data.source_ap = ap::port_select::a; - host_command->bus_data.data_size = 0; + host_command->bus_data.data_size = data_size / 4; +} + +void maple_init_device_request(uint32_t * buf, uint32_t * receive_address) +{ + maple_init_host_command(buf, receive_address, device_request::command_code, (sizeof (struct device_request::data_fields))); +} + +void maple_init_get_condition(uint32_t * buf, uint32_t * receive_address) +{ + maple_init_host_command(buf, receive_address, get_condition::command_code, (sizeof (struct get_condition::data_fields))); + + auto host_command = reinterpret_cast *>(buf); + + auto& function_type = host_command->bus_data.data_fields.function_type; + // controller function type + function_type[0] = 0x00; + function_type[1] = 0x00; + function_type[2] = 0x00; + function_type[3] = 0x01; } void maple_dma_start(uint32_t * command_buf) @@ -52,29 +73,28 @@ void maple_dma_start(uint32_t * command_buf) // disable maple-DMA maple_if.MDEN = mden::dma_enable::abort; - volatile uint32_t _dummy = maple_if.MDST; - (void)_dummy; + while (mdst::start_status::status(maple_if.MDST) != 0); // 20nsec * 0xc350 = 1ms constexpr uint32_t one_msec = 0xc350; maple_if.MSYS = msys::time_out_counter(one_msec) | msys::sending_rate::_2M; - maple_if.MDTSEL = mdtsel::trigger_select::software_initiation; - /* top address: the first/lowest address bottom address: the last/highest address */ maple_if.MDAPRO = mdapro::security_code | mdapro::top_address(0x00) | mdapro::bottom_address(0x7f); + maple_if.MDTSEL = mdtsel::trigger_select::software_initiation; + maple_if.MDSTAR = mdstar::table_address(reinterpret_cast(command_buf)); maple_if.MDEN = mden::dma_enable::enable; maple_if.MDST = mdst::start_status::start; // wait for completion + //while (mdst::start_status::status(maple_if.MDST) != 0); while ((system.ISTNRM & ISTNRM__END_OF_DMA_MAPLE_DMA) == 0); - system.ISTNRM = ISTNRM__END_OF_DMA_MAPLE_DMA; } diff --git a/maple/maple.h b/maple/maple.h index a0cd7d4..f756759 100644 --- a/maple/maple.h +++ b/maple/maple.h @@ -3,4 +3,6 @@ #include void maple_init_host_command(uint32_t * buf, uint32_t * receive_address); +void maple_init_device_request(uint32_t * buf, uint32_t * receive_address); +void maple_init_get_condition(uint32_t * buf, uint32_t * receive_address); void maple_dma_start(uint32_t * command_buf); diff --git a/maple/maple_bus_commands.h b/maple/maple_bus_commands.h index 259b86c..68b90fc 100644 --- a/maple/maple_bus_commands.h +++ b/maple/maple_bus_commands.h @@ -47,7 +47,7 @@ namespace device_status { namespace device_all_status { constexpr uint32_t command_code = 0x6; - template + template struct data_fields { uint8_t device_id[16]; uint8_t destination_code[1]; @@ -56,10 +56,10 @@ namespace device_all_status { uint8_t license[60]; uint8_t low_consumption_standby_current[2]; uint8_t maximum_current_consumption[2]; - uint8_t free_device_status[N]; + T free_device_status; }; - static_assert((sizeof (struct data_fields<0>)) == 112); + static_assert((sizeof (struct data_fields)) == 112); } namespace device_reply { @@ -72,12 +72,13 @@ namespace device_reply { namespace data_transfer { constexpr uint32_t command_code = 0x8; - template + template struct data_fields { - uint8_t data[N]; + uint8_t function_type[4]; + T data; }; - static_assert((sizeof (struct data_fields<0>)) == 0); + static_assert((sizeof (struct data_fields)) == 4); } namespace get_condition { @@ -117,16 +118,16 @@ namespace block_read { namespace block_write { constexpr uint32_t command_code = 0xc; - template + template struct data_fields { uint8_t function_type[4]; uint8_t pt[1]; uint8_t phase[1]; uint8_t block_no[2]; - uint8_t written_data[N]; + T written_data; }; - static_assert((sizeof (struct data_fields<0>)) == 8); + static_assert((sizeof (struct data_fields)) == 8); } namespace get_last_error { @@ -145,37 +146,37 @@ namespace get_last_error { namespace set_condition { constexpr uint32_t command_code = 0xe; - template + template struct data_fields { uint8_t function_type[4]; - uint8_t write_in_data[N]; + T write_in_data; }; - static_assert((sizeof (struct data_fields<0>)) == 4); + static_assert((sizeof (struct data_fields)) == 4); } namespace ft4_control { constexpr uint32_t command_code = 0xf; - template + template struct data_fields { uint8_t function_type[4]; - uint8_t ft4_data[N]; + T ft4_data; }; - static_assert((sizeof (struct data_fields<0>)) == 4); + static_assert((sizeof (struct data_fields)) == 4); } namespace ar_control { constexpr uint32_t command_code = 0x10; - template + template struct data_fields { uint8_t function_type[4]; - uint8_t data[N]; + T data; }; - static_assert((sizeof (struct data_fields<0>)) == 4); + static_assert((sizeof (struct data_fields)) == 4); } namespace function_type_unknown { diff --git a/maple/maple_bus_ft0.h b/maple/maple_bus_ft0.h new file mode 100644 index 0000000..3c37d7a --- /dev/null +++ b/maple/maple_bus_ft0.h @@ -0,0 +1,34 @@ +namespace ft0 { + namespace data_transfer { + namespace digital_button { + constexpr uint32_t ra = 1 << 7; + constexpr uint32_t la = 1 << 6; + constexpr uint32_t da = 1 << 5; + constexpr uint32_t ua = 1 << 4; + constexpr uint32_t start = 1 << 3; + constexpr uint32_t a = 1 << 2; + constexpr uint32_t b = 1 << 1; + constexpr uint32_t c = 1 << 0; + constexpr uint32_t rb = 1 << 15; + constexpr uint32_t lb = 1 << 14; + constexpr uint32_t db = 1 << 13; + constexpr uint32_t ub = 1 << 12; + constexpr uint32_t d = 1 << 11; + constexpr uint32_t x = 1 << 10; + constexpr uint32_t y = 1 << 9; + constexpr uint32_t z = 1 << 8; + } + + struct data_format { + uint16_t digital_button; + uint8_t analog_axis_1; + uint8_t analog_axis_2; + uint8_t analog_axis_3; + uint8_t analog_axis_4; + uint8_t analog_axis_5; + uint8_t analog_axis_6; + }; + static_assert((sizeof (struct data_format)) == 8); + } +} + diff --git a/maple_bus_commands.h b/maple_bus_commands.h deleted file mode 100644 index 18c41cc..0000000 --- a/maple_bus_commands.h +++ /dev/null @@ -1,225 +0,0 @@ -#include -#include - -#include "type.h" - -namespace device_request { - constexpr uint32_t command_code = 0x1; - struct data_fields { - }; -} - -namespace all_status_request { - constexpr uint32_t command_code = 0x2; - struct data_fields { - }; -} - -namespace device_reset { - constexpr uint32_t command_code = 0x3; - struct data_fields { - }; -} - -namespace device_kill { - constexpr uint32_t command_code = 0x4; - struct data_fields { - }; -} - -namespace device_status { - constexpr uint32_t command_code = 0x5; - struct data_fields { - - uint8_t device_id[16]; - uint8_t destination_code[1]; - uint8_t connection_direction[1]; - uint8_t product_name[30]; - uint8_t license[60]; - uint8_t low_consumption_standby_current[2]; - uint8_t maximum_current_consumption[2]; - }; - - static_assert((sizeof (struct data_fields)) == 112); -} - -namespace device_all_status { - constexpr uint32_t command_code = 0x6; - struct data_fields { - - template - uint8_t device_id[16]; - uint8_t destination_code[1]; - uint8_t connection_direction[1]; - uint8_t product_name[30]; - uint8_t license[60]; - uint8_t low_consumption_standby_current[2]; - uint8_t maximum_current_consumption[2]; - uint8_t free_device_status[N]; - }; - - static_assert((sizeof (struct data_fields<0>)) == 112); -} - -namespace device_reply { - constexpr uint32_t command_code = 0x7; - struct data_fields { - }; -} - -namespace data_transfer { - constexpr uint32_t command_code = 0x8; - struct data_fields { - - template - uint8_t data[N]; - }; - - static_assert((sizeof (struct data_fields<0>)) == 0); -} - -namespace get_condition { - constexpr uint32_t command_code = 0x9; - struct data_fields { - - uint8_t function_type[4]; - }; - - static_assert((sizeof (struct data_fields)) == 4); -} - -namespace get_media_info { - constexpr uint32_t command_code = 0xa; - struct data_fields { - - uint8_t function_type[4]; - uint8_t pt[4]; - }; - - static_assert((sizeof (struct data_fields)) == 8); -} - -namespace block_read { - constexpr uint32_t command_code = 0xb; - struct data_fields { - - uint8_t function_type[4]; - uint8_t pt[1]; - uint8_t phase[1]; - uint8_t block_no[2]; - }; - - static_assert((sizeof (struct data_fields)) == 8); -} - -namespace block_write { - constexpr uint32_t command_code = 0xc; - struct data_fields { - - template - uint8_t function_type[4]; - uint8_t pt[1]; - uint8_t phase[1]; - uint8_t block_no[2]; - uint8_t written_data[N]; - }; - - static_assert((sizeof (struct data_fields<0>)) == 8); -} - -namespace get_last_error { - constexpr uint32_t command_code = 0xd; - struct data_fields { - - uint8_t function_type[4]; - uint8_t pt[1]; - uint8_t phase[1]; - uint8_t block_no[2]; - }; - - static_assert((sizeof (struct data_fields)) == 8); -} - -namespace set_condition { - constexpr uint32_t command_code = 0xe; - struct data_fields { - - template - uint8_t function_type[4]; - uint8_t write_in_data[N]; - }; - - static_assert((sizeof (struct data_fields<0>)) == 4); -} - -namespace ft4_control { - constexpr uint32_t command_code = 0xf; - struct data_fields { - - template - uint8_t function_type[4]; - uint8_t ft4_data[N]; - }; - - static_assert((sizeof (struct data_fields<0>)) == 4); -} - -namespace ar_control { - constexpr uint32_t command_code = 0x10; - struct data_fields { - - template - uint8_t function_type[4]; - uint8_t data[N]; - }; - - static_assert((sizeof (struct data_fields<0>)) == 4); -} - -namespace function_type_unknown { - constexpr uint32_t command_code = 0xfe; - struct data_fields { - }; -} - -namespace command_unknown { - constexpr uint32_t command_code = 0xfd; - struct data_fields { - }; -} - -namespace transmit_again { - constexpr uint32_t command_code = 0xfc; - struct data_fields { - }; -} - -namespace file_error { - constexpr uint32_t command_code = 0xfb; - struct data_fields { - - uint8_t function_error_code[4]; - }; - - static_assert((sizeof (struct data_fields)) == 4); -} - -namespace lcd_error { - constexpr uint32_t command_code = 0xfa; - struct data_fields { - - uint8_t function_error_code[4]; - }; - - static_assert((sizeof (struct data_fields)) == 4); -} - -namespace ar_error { - constexpr uint32_t command_code = 0xf9; - struct data_fields { - - uint8_t function_error_code[4]; - }; - - static_assert((sizeof (struct data_fields)) == 4); -} diff --git a/regs/gen/maple_commands.py b/regs/gen/maple_commands.py index 30dd322..6429bb1 100644 --- a/regs/gen/maple_commands.py +++ b/regs/gen/maple_commands.py @@ -27,7 +27,7 @@ def command_namespace(namespace: CommandNamespace, if variable is not None: assert variable.lower() == "n" - yield "template " + yield "template " yield "struct data_fields {" @@ -37,7 +37,7 @@ def command_namespace(namespace: CommandNamespace, yield f"uint8_t {field_name}[{const}];" elif const == 0: assert var == "n" - yield f"uint8_t {field_name}[{var.upper()}];" + yield f"T {field_name};" else: yield f"uint8_t {field_name}[{const} + {var.upper()}];" @@ -47,7 +47,7 @@ def command_namespace(namespace: CommandNamespace, if variable is not None: assert variable == "n" - yield f"static_assert((sizeof (struct data_fields<0>)) == {length});" + yield f"static_assert((sizeof (struct data_fields)) == {length});" else: yield f"static_assert((sizeof (struct data_fields)) == {length});" diff --git a/regs/gen/maple_data_format.py b/regs/gen/maple_data_format.py new file mode 100644 index 0000000..116c713 --- /dev/null +++ b/regs/gen/maple_data_format.py @@ -0,0 +1,124 @@ +import csv +import sys +from dataclasses import dataclass +from collections import defaultdict + +from generate import renderer + +@dataclass +class Field: + name: str + bits: list[str] + +@dataclass +class Format: + name: str + fields: list[Field] + field_order: list[str] + size: int + +def read_input(filename): + with open(filename) as f: + reader = csv.reader(f, delimiter=",", quotechar='"') + rows = [ + [s.strip() for s in row] + for row in reader + ] + return rows + +def parse_data_format(ix, rows): + if ix >= len(rows): + return None + + while rows[ix][0] == "": + ix += 1 + if ix >= len(rows): + return None + + format_name, *header = rows[ix] + ix += 1 + assert format_name != "" + assert header == ["7", "6", "5", "4", "3", "2", "1", "0"] + + fields = defaultdict(list) + field_order = list() + size = 0 + while ix < len(rows) and rows[ix][0] != "": + field_name, *_bits = rows[ix] + ix += 1 + excess_bits = [b for b in _bits[8:] if b != ""] + assert excess_bits == [] + bits = [b for b in _bits[:8] if b != ""] + assert len(bits) in {0, 8}, bits + fields[field_name].append(Field(field_name, list(bits))) + size += 1 + if field_name not in field_order: + field_order.append(field_name) + + return ix, Format(format_name, dict(fields), field_order, size) + +def parse(rows): + ix = 0 + formats = [] + + while True: + ix_format = parse_data_format(ix, rows) + if ix_format is None: + break + ix, format = ix_format + formats.append(format) + + assert len(formats) > 0 + return formats + +bit_order = [7, 6, 5, 4, 3, 2, 1, 0] + +def render_format(format): + yield f"namespace {format.name} {{" + for field_name in format.field_order: + subfields = format.fields[field_name] + if not any(field.bits != [] for field in subfields): + continue + + yield f"namespace {field_name} {{" + for ix, field in enumerate(subfields): + bit_offset = 8 * ix + if field.bits != []: + assert len(field.bits) == 8 + for byte_ix, bit in zip(bit_order, field.bits): + bit_ix = byte_ix + bit_offset + yield f"constexpr uint32_t {bit.lower()} = 1 << {bit_ix};" + yield "}" + yield "" + + yield f"struct data_format {{" + + for field_name in format.field_order: + subfields = format.fields[field_name] + if len(subfields) == 1: + field, = subfields + yield f"uint8_t {field_name};" + elif len(subfields) == 2: + yield f"uint16_t {field_name};" + else: + assert False, len(subfields) + + yield "};" + assert format.size % 4 == 0, format.size + yield f"static_assert((sizeof (struct data_format)) == {format.size});" + yield "}" + +def render_formats(name, formats): + yield f"namespace {name} {{" + for format in formats: + yield from render_format(format) + yield "}" + +if __name__ == "__main__": + rows = read_input(sys.argv[1]) + name = sys.argv[1].split('.')[0].split('_')[-1] + assert len(name) == 3 or len(name) == 4 + formats = parse(rows) + render, out = renderer() + render(render_formats(name, formats)) + print(out.getvalue()) diff --git a/regs/maple_bus_commands.csv b/regs/maple_bus_commands.csv index e3700de..a065431 100644 --- a/regs/maple_bus_commands.csv +++ b/regs/maple_bus_commands.csv @@ -26,7 +26,8 @@ ,,,,, "device_reply","peripheral","0x07","0x00",, ,,,,, -"data_transfer","peripheral","0x08","n/4","data","n" +"data_transfer","peripheral","0x08","0x01+n/4","function_type",4 +"data_transfer",,,,"data","n" ,,,,, "get_condition","host","0x09","0x01","function_type",4 ,,,,, diff --git a/regs/maple_bus_commands.ods b/regs/maple_bus_commands.ods index abd09bd2cac20401c14a357a10846ed82f420449..1b56cf8674b2cb88901396ab6446d91b8e43dd6b 100644 GIT binary patch delta 6733 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 delta 6664 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 O71EEF7~78X4*5TjPVHC# delta 5825 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 kU4L}{%5A0)V-`2YX_ diff --git a/scene.cpp b/scene.cpp index 5212a62..4896bad 100644 --- a/scene.cpp +++ b/scene.cpp @@ -49,11 +49,11 @@ struct scene_quad_ta_parameters { static_assert((sizeof (scene_quad_ta_parameters)) == 32 * 4); -uint32_t scene_transform_quad(uint32_t * _scene) +uint32_t scene_transform_quad(uint32_t * _scene, uint32_t base_color) { auto scene = reinterpret_cast(&_scene[0]); - uint32_t base_color = 0xffffff00; + //uint32_t base_color = 0xffffff00; scene->sprite = global_sprite(base_color); scene->vertex = vertex_sprite_type_0(scene_quad[0].x * 240 + 320, scene_quad[0].y * 240 + 240, diff --git a/scene.h b/scene.h index 0bade7f..9a6eade 100644 --- a/scene.h +++ b/scene.h @@ -2,6 +2,6 @@ #include -uint32_t scene_transform_quad(uint32_t * scene); +uint32_t scene_transform_quad(uint32_t * scene, uint32_t color); uint32_t scene_transform(uint32_t * scene);