From 7daff7e3ca953f08acf585f493cd3093352cfdd3 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sat, 16 Dec 2023 23:28:28 +0800 Subject: [PATCH] example: add macaw_twiddle --- example/example.mk | 12 +++ example/macaw.cpp | 2 +- example/macaw_twiddle.cpp | 162 ++++++++++++++++++++++++++++++++++++++ holly.hpp | 142 ++++++++++++++++----------------- holly/isp_tsp.hpp | 5 +- holly/ta_parameter.hpp | 2 +- regs/gen/sh7091.py | 6 +- regs/maple_bus_bits.ods | Bin 15709 -> 14517 bytes twiddle.hpp | 72 +++++++++++++++++ 9 files changed, 326 insertions(+), 77 deletions(-) create mode 100644 example/macaw_twiddle.cpp create mode 100644 twiddle.hpp diff --git a/example/example.mk b/example/example.mk index 5f92511..62fc5b8 100644 --- a/example/example.mk +++ b/example/example.mk @@ -21,6 +21,18 @@ MACAW_OBJ = \ example/macaw.elf: LDSCRIPT = $(LIB)/alt.lds example/macaw.elf: $(START_OBJ) $(MACAW_OBJ) +MACAW_TWIDDLE_OBJ = \ + example/macaw_twiddle.o \ + vga.o \ + holly/core.o \ + holly/region_array.o \ + holly/background.o \ + holly/ta_fifo_polygon_converter.o \ + macaw.data.o + +example/macaw_twiddle.elf: LDSCRIPT = $(LIB)/alt.lds +example/macaw_twiddle.elf: $(START_OBJ) $(MACAW_TWIDDLE_OBJ) + MACAW_MULTIPASS_OBJ = \ example/macaw_multipass.o \ vga.o \ diff --git a/example/macaw.cpp b/example/macaw.cpp index fceac0b..71a957a 100644 --- a/example/macaw.cpp +++ b/example/macaw.cpp @@ -113,7 +113,7 @@ void main() constexpr uint32_t ta_alloc = ta_alloc_ctrl::pt_opb::no_list | ta_alloc_ctrl::tm_opb::no_list - | ta_alloc_ctrl::t_opb::_16x4byte + | ta_alloc_ctrl::t_opb::no_list | ta_alloc_ctrl::om_opb::no_list | ta_alloc_ctrl::o_opb::_16x4byte; diff --git a/example/macaw_twiddle.cpp b/example/macaw_twiddle.cpp new file mode 100644 index 0000000..c5d46cb --- /dev/null +++ b/example/macaw_twiddle.cpp @@ -0,0 +1,162 @@ +#include + +#include "align.hpp" +#include "vga.hpp" + +#include "holly/texture_memory_alloc.hpp" +#include "holly.hpp" +#include "holly/core.hpp" +#include "holly/core_bits.hpp" +#include "holly/ta_fifo_polygon_converter.hpp" +#include "holly/ta_parameter.hpp" +#include "holly/ta_bits.hpp" +#include "holly/region_array.hpp" +#include "holly/background.hpp" +#include "memorymap.hpp" +#include "twiddle.hpp" + +#include "macaw.hpp" + +struct vertex { + float x; + float y; + float z; + float u; + float v; + uint32_t color; +}; + +const struct vertex strip_vertices[4] = { + // [ position ] [ uv coordinates ] [color ] + { -0.5f, 0.5f, 0.f, 0.f , 127.f/128.f, 0x00000000}, // the first two base colors in a + { -0.5f, -0.5f, 0.f, 0.f , 0.f , 0x00000000}, // non-Gouraud triangle strip are ignored + { 0.5f, 0.5f, 0.f, 127.f/128.f, 127.f/128.f, 0x00000000}, + { 0.5f, -0.5f, 0.f, 127.f/128.f, 0.f , 0x00000000}, +}; +constexpr uint32_t strip_length = (sizeof (strip_vertices)) / (sizeof (struct vertex)); + +static float theta = 0; +constexpr float half_degree = 0.01745329f / 2.f; + +uint32_t transform(uint32_t * ta_parameter_buf, + const vertex * strip_vertices, + const uint32_t strip_length) +{ + auto parameter = ta_parameter_writer(ta_parameter_buf); + uint32_t texture_address = (offsetof (struct texture_memory_alloc, texture)); + auto polygon = global_polygon_type_0(texture_address); + polygon.texture_control_word = texture_control_word::pixel_format::_565 + | texture_control_word::scan_order::twiddled + | texture_control_word::texture_address(texture_address / 8); + parameter.append() = polygon; + + for (uint32_t i = 0; i < strip_length; i++) { + bool end_of_strip = i == strip_length - 1; + + float x = strip_vertices[i].x; + float y = strip_vertices[i].y; + float z = strip_vertices[i].z; + float x1; + + x1 = x * __builtin_cosf(theta) - z * __builtin_sinf(theta); + z = x * __builtin_sinf(theta) + z * __builtin_cosf(theta); + x = x1; + x *= 240.f; + y *= 240.f; + x += 320.f; + y += 240.f; + z = 1.f / (z + 10.f); + + parameter.append() = + vertex_polygon_type_3(x, y, z, + strip_vertices[i].u, + strip_vertices[i].v, + strip_vertices[i].color, + end_of_strip); + } + + parameter.append() = global_end_of_list(); + + return parameter.offset; +} + +void init_texture_memory(const struct opb_size& opb_size) +{ + volatile texture_memory_alloc * mem = reinterpret_cast(texture_memory); + + background_parameter(mem->background); + + region_array2(mem->region_array, + (offsetof (struct texture_memory_alloc, object_list)), + 640 / 32, // width + 480 / 32, // height + opb_size + ); +} + +uint32_t _ta_parameter_buf[((32 * (strip_length + 2)) + 32) / 4]; + +void main() +{ + vga(); + + auto src = reinterpret_cast(&_binary_macaw_data_start); + auto size = reinterpret_cast(&_binary_macaw_data_size); + auto mem = reinterpret_cast(0xa400'0000); + + uint16_t temp[size / 3]; + for (uint32_t px = 0; px < size / 3; px++) { + uint8_t r = src[px * 3 + 0]; + uint8_t g = src[px * 3 + 1]; + uint8_t b = src[px * 3 + 2]; + + uint16_t rgb565 = ((r / 8) << 11) | ((g / 4) << 5) | ((b / 8) << 0); + temp[px] = rgb565; + } + twiddle::texture(mem->texture, temp, 128, 128); + + // The address of `ta_parameter_buf` must be a multiple of 32 bytes. + // This is mandatory for ch2-dma to the ta fifo polygon converter. + uint32_t * ta_parameter_buf = align_32byte(_ta_parameter_buf); + + constexpr uint32_t ta_alloc = ta_alloc_ctrl::pt_opb::no_list + | ta_alloc_ctrl::tm_opb::no_list + | ta_alloc_ctrl::t_opb::no_list + | ta_alloc_ctrl::om_opb::no_list + | ta_alloc_ctrl::o_opb::_16x4byte; + + constexpr struct opb_size opb_size = { .opaque = 16 * 4 + , .opaque_modifier = 0 + , .translucent = 0 + , .translucent_modifier = 0 + , .punch_through = 0 + }; + + constexpr uint32_t tiles = (640 / 32) * (320 / 32); + + holly.SOFTRESET = softreset::pipeline_soft_reset + | softreset::ta_soft_reset; + holly.SOFTRESET = 0; + + core_init(); + init_texture_memory(opb_size); + + uint32_t frame_ix = 0; + constexpr uint32_t num_frames = 1; + + while (true) { + ta_polygon_converter_init(opb_size.total() * tiles, ta_alloc); + uint32_t ta_parameter_size = transform(ta_parameter_buf, strip_vertices, strip_length); + ta_polygon_converter_transfer(ta_parameter_buf, ta_parameter_size); + ta_wait_opaque_list(); + + core_start_render(frame_ix, num_frames); + + v_sync_out(); + v_sync_in(); + core_wait_end_of_render_video(frame_ix, num_frames); + + theta += half_degree; + frame_ix += 1; + } +} diff --git a/holly.hpp b/holly.hpp index 6458a44..24952c6 100644 --- a/holly.hpp +++ b/holly.hpp @@ -4,92 +4,92 @@ #include "type.hpp" struct holly_reg { - reg32 ID; /* Device ID */ - reg32 REVISION; /* Revision Number */ - reg32 SOFTRESET; /* CORE & TA software reset */ + reg32 ID; /* Device ID */ + reg32 REVISION; /* Revision Number */ + reg32 SOFTRESET; /* CORE & TA software reset */ reg8 _pad0[8]; - reg32 STARTRENDER; /* Drawing start */ - reg32 TEST_SELECT; /* Test (writing this register is prohibited) */ + reg32 STARTRENDER; /* Drawing start */ + reg32 TEST_SELECT; /* Test (writing this register is prohibited) */ reg8 _pad1[4]; - reg32 PARAM_BASE; /* Base address for ISP parameters */ + reg32 PARAM_BASE; /* Base address for ISP parameters */ reg8 _pad2[8]; - reg32 REGION_BASE; /* Base address for Region Array */ - reg32 SPAN_SORT_CFG; /* Span Sorter control */ + reg32 REGION_BASE; /* Base address for Region Array */ + reg32 SPAN_SORT_CFG; /* Span Sorter control */ reg8 _pad3[12]; - reg32 VO_BORDER_COL; /* Border area color */ - reg32 FB_R_CTRL; /* Frame buffer read control */ - reg32 FB_W_CTRL; /* Frame buffer write control */ - reg32 FB_W_LINESTRIDE; /* Frame buffer line stride */ - reg32 FB_R_SOF1; /* Read start address for field - 1/strip - 1 */ - reg32 FB_R_SOF2; /* Read start address for field - 2/strip - 2 */ + reg32 VO_BORDER_COL; /* Border area color */ + reg32 FB_R_CTRL; /* Frame buffer read control */ + reg32 FB_W_CTRL; /* Frame buffer write control */ + reg32 FB_W_LINESTRIDE; /* Frame buffer line stride */ + reg32 FB_R_SOF1; /* Read start address for field - 1/strip - 1 */ + reg32 FB_R_SOF2; /* Read start address for field - 2/strip - 2 */ reg8 _pad4[4]; - reg32 FB_R_SIZE; /* Frame buffer XY size */ - reg32 FB_W_SOF1; /* Write start address for field - 1/strip - 1 */ - reg32 FB_W_SOF2; /* Write start address for field - 2/strip - 2 */ - reg32 FB_X_CLIP; /* Pixel clip X coordinate */ - reg32 FB_Y_CLIP; /* Pixel clip Y coordinate */ + reg32 FB_R_SIZE; /* Frame buffer XY size */ + reg32 FB_W_SOF1; /* Write start address for field - 1/strip - 1 */ + reg32 FB_W_SOF2; /* Write start address for field - 2/strip - 2 */ + reg32 FB_X_CLIP; /* Pixel clip X coordinate */ + reg32 FB_Y_CLIP; /* Pixel clip Y coordinate */ reg8 _pad5[4]; - reg32 FPU_SHAD_SCALE; /* Intensity Volume mode */ - reg32 FPU_CULL_VAL; /* Comparison value for culling */ - reg32 FPU_PARAM_CFG; /* Parameter read control */ - reg32 HALF_OFFSET; /* Pixel sampling control */ - reg32 FPU_PERP_VAL; /* Comparison value for perpendicular polygons */ - reg32 ISP_BACKGND_D; /* Background surface depth */ - reg32 ISP_BACKGND_T; /* Background surface tag */ + reg32 FPU_SHAD_SCALE; /* Intensity Volume mode */ + reg32 FPU_CULL_VAL; /* Comparison value for culling */ + reg32 FPU_PARAM_CFG; /* Parameter read control */ + reg32 HALF_OFFSET; /* Pixel sampling control */ + reg32 FPU_PERP_VAL; /* Comparison value for perpendicular polygons */ + reg32 ISP_BACKGND_D; /* Background surface depth */ + reg32 ISP_BACKGND_T; /* Background surface tag */ reg8 _pad6[8]; - reg32 ISP_FEED_CFG; /* Translucent polygon sort mode */ + reg32 ISP_FEED_CFG; /* Translucent polygon sort mode */ reg8 _pad7[4]; - reg32 SDRAM_REFRESH; /* Texture memory refresh counter */ - reg32 SDRAM_ARB_CFG; /* Texture memory arbiter control */ - reg32 SDRAM_CFG; /* Texture memory control */ + reg32 SDRAM_REFRESH; /* Texture memory refresh counter */ + reg32 SDRAM_ARB_CFG; /* Texture memory arbiter control */ + reg32 SDRAM_CFG; /* Texture memory control */ reg8 _pad8[4]; - reg32 FOG_COL_RAM; /* Color for Look Up table Fog */ - reg32 FOG_COL_VERT; /* Color for vertex Fog */ - reg32 FOG_DENSITY; /* Fog scale value */ - reg32 FOG_CLAMP_MAX; /* Color clamping maximum value */ - reg32 FOG_CLAMP_MIN; /* Color clamping minimum value */ - reg32 SPG_TRIGGER_POS; /* External trigger signal HV counter value */ - reg32 SPG_HBLANK_INT; /* H-blank interrupt control */ - reg32 SPG_VBLANK_INT; /* V-blank interrupt control */ - reg32 SPG_CONTROL; /* Sync pulse generator control */ - reg32 SPG_HBLANK; /* H-blank control */ - reg32 SPG_LOAD; /* HV counter load value */ - reg32 SPG_VBLANK; /* V-blank control */ - reg32 SPG_WIDTH; /* Sync width control */ - reg32 TEXT_CONTROL; /* Texturing control */ - reg32 VO_CONTROL; /* Video output control */ - reg32 VO_STARTX; /* Video output start X position */ - reg32 VO_STARTY; /* Video output start Y position */ - reg32 SCALER_CTL; /* X & Y scaler control */ + reg32 FOG_COL_RAM; /* Color for Look Up table Fog */ + reg32 FOG_COL_VERT; /* Color for vertex Fog */ + reg32 FOG_DENSITY; /* Fog scale value */ + reg32 FOG_CLAMP_MAX; /* Color clamping maximum value */ + reg32 FOG_CLAMP_MIN; /* Color clamping minimum value */ + reg32 SPG_TRIGGER_POS; /* External trigger signal HV counter value */ + reg32 SPG_HBLANK_INT; /* H-blank interrupt control */ + reg32 SPG_VBLANK_INT; /* V-blank interrupt control */ + reg32 SPG_CONTROL; /* Sync pulse generator control */ + reg32 SPG_HBLANK; /* H-blank control */ + reg32 SPG_LOAD; /* HV counter load value */ + reg32 SPG_VBLANK; /* V-blank control */ + reg32 SPG_WIDTH; /* Sync width control */ + reg32 TEXT_CONTROL; /* Texturing control */ + reg32 VO_CONTROL; /* Video output control */ + reg32 VO_STARTX; /* Video output start X position */ + reg32 VO_STARTY; /* Video output start Y position */ + reg32 SCALER_CTL; /* X & Y scaler control */ reg8 _pad9[16]; - reg32 PAL_RAM_CTRL; /* Palette RAM control */ - reg32 SPG_STATUS; /* Sync pulse generator status */ - reg32 FB_BURSTCTRL; /* Frame buffer burst control */ - reg32 FB_C_SOF; /* Current frame buffer start address */ - reg32 Y_COEFF; /* Y scaling coefficent */ - reg32 PT_ALPHA_REF; /* Alpha value for Punch Through polygon comparison */ + reg32 PAL_RAM_CTRL; /* Palette RAM control */ + reg32 SPG_STATUS; /* Sync pulse generator status */ + reg32 FB_BURSTCTRL; /* Frame buffer burst control */ + reg32 FB_C_SOF; /* Current frame buffer start address */ + reg32 Y_COEFF; /* Y scaling coefficent */ + reg32 PT_ALPHA_REF; /* Alpha value for Punch Through polygon comparison */ reg8 _pad10[4]; - reg32 TA_OL_BASE; /* Object List write start address */ - reg32 TA_ISP_BASE; /* ISP/TSP Parameter write start address */ - reg32 TA_OL_LIMIT; /* Object List write limit address */ - reg32 TA_ISP_LIMIT; /* ISP/TSP Parameter limit address */ - reg32 TA_NEXT_OPB; /* Start address for the Object Pointer Block */ - reg32 TA_ITP_CURRENT; /* Starting address where the next ISP/TSP Parameters are stored */ - reg32 TA_GLOB_TILE_CLIP; /* Global Tile Clip control */ - reg32 TA_ALLOC_CTRL; /* Object list control */ - reg32 TA_LIST_INIT; /* TA initialization */ - reg32 TA_YUV_TEX_BASE; /* YUV422 texture write start address */ - reg32 TA_YUV_TEX_CTRL; /* YUV converter control */ - reg32 TA_YUV_TEX_CNT; /* YUV converter macro block counter value */ + reg32 TA_OL_BASE; /* Object List write start address */ + reg32 TA_ISP_BASE; /* ISP/TSP Parameter write start address */ + reg32 TA_OL_LIMIT; /* Object List write limit address */ + reg32 TA_ISP_LIMIT; /* ISP/TSP Parameter limit address */ + reg32 TA_NEXT_OPB; /* Start address for the Object Pointer Block */ + reg32 TA_ITP_CURRENT; /* Starting address where the next ISP/TSP Parameters are stored */ + reg32 TA_GLOB_TILE_CLIP; /* Global Tile Clip control */ + reg32 TA_ALLOC_CTRL; /* Object list control */ + reg32 TA_LIST_INIT; /* TA initialization */ + reg32 TA_YUV_TEX_BASE; /* YUV422 texture write start address */ + reg32 TA_YUV_TEX_CTRL; /* YUV converter control */ + reg32 TA_YUV_TEX_CNT; /* YUV converter macro block counter value */ reg8 _pad11[12]; - reg32 TA_LIST_CONT; /* TA continuation processing */ - reg32 TA_NEXT_OPB_INIT; /* Additional OPB starting address */ + reg32 TA_LIST_CONT; /* TA continuation processing */ + reg32 TA_NEXT_OPB_INIT; /* Additional OPB starting address */ reg8 _pad12[152]; - reg8 FOG_TABLE[512]; /* Look-up table fog data */ + reg32 FOG_TABLE[128]; /* Look-up table fog data */ reg8 _pad13[512]; - reg8 TA_OL_POINTERS[2400];/* TA Object List Pointer data */ + reg32 TA_OL_POINTERS[600]; /* TA Object List Pointer data */ reg8 _pad14[160]; - reg8 PALETTE_RAM[4096]; /* Palette RAM */ + reg32 PALETTE_RAM[1024]; /* Palette RAM */ }; static_assert((offsetof (struct holly_reg, ID)) == 0x0); diff --git a/holly/isp_tsp.hpp b/holly/isp_tsp.hpp index dc6339d..4a468f3 100644 --- a/holly/isp_tsp.hpp +++ b/holly/isp_tsp.hpp @@ -137,7 +137,10 @@ namespace texture_control_word { constexpr uint32_t _8bpp_palette = 6 << 27; } - constexpr uint32_t scan_order = 1 << 26; + namespace scan_order { + constexpr uint32_t twiddled = 0 << 26; + constexpr uint32_t non_twiddled = 1 << 26; + } constexpr uint32_t stride_select = 1 << 25; // in 8-byte units diff --git a/holly/ta_parameter.hpp b/holly/ta_parameter.hpp index a30b255..b0d2daf 100644 --- a/holly/ta_parameter.hpp +++ b/holly/ta_parameter.hpp @@ -186,7 +186,7 @@ struct global_polygon_type_0 { | tsp_instruction_word::texture_v_size::_128 ) // 128px , texture_control_word( texture_control_word::pixel_format::_565 - | texture_control_word::scan_order // non-twiddled + | texture_control_word::scan_order::non_twiddled | texture_control_word::texture_address(texture_address / 8) ) , _res0(0) diff --git a/regs/gen/sh7091.py b/regs/gen/sh7091.py index 63f88a6..a3cf7c0 100644 --- a/regs/gen/sh7091.py +++ b/regs/gen/sh7091.py @@ -95,10 +95,10 @@ def new_writer(): type = size_to_type(size) return f"{type} {name};" else: - type = size_to_type(1) - return f"{type} {name}[{size}];" + type = size_to_type(4) + return f"{type} {name}[{size // 4}];" - yield field().ljust(25) + f"/* {description} */" + yield field().ljust(27) + f"/* {description} */" stack.append((address, name)) last_address = address + size diff --git a/regs/maple_bus_bits.ods b/regs/maple_bus_bits.ods index 3c5318cbecaa18542e7355448000ef8e510f69d9..7c93f534bea43b7ef077229ce2600b370396b55b 100644 GIT binary patch delta 12450 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 diff --git a/twiddle.hpp b/twiddle.hpp new file mode 100644 index 0000000..0c0075d --- /dev/null +++ b/twiddle.hpp @@ -0,0 +1,72 @@ +#include + +namespace twiddle { + +/* +This reproduces the twiddle index table shown in +"3.6.2.1 Twiddled Format". + + x → + 000 001 010 011 + -------------------------------- + | xyxyxy xyxyxy xyxyxy xyxyxy + |=============================== + y 000 | 000000 000010 001000 001010 + ↓ 001 | 000001 000011 001001 001011 + 010 | 000100 000110 001100 001110 + 011 | 000101 000111 001101 001111 + +alternately, in verilog syntax: + + + input [2:0] x; // x coordinate + input [2:0] y; // y coordinate + output [5:0] t; // twiddled index + assign t = {x[2], y[2], x[1], y[1], x[0], y[0]}; +*/ + +constexpr inline uint32_t from_xy(uint32_t x, uint32_t y) +{ + // maximum texture size : 1024x1024 + // maximum 1-dimensional index: 0xfffff + // bits : 19-0 + + uint32_t twiddle_ix = 0; + for (int i = 0; i <= (19 / 2); i++) { + twiddle_ix |= ((y >> i) & 1) << (i * 2 + 0); + twiddle_ix |= ((x >> i) & 1) << (i * 2 + 1); + } + + return twiddle_ix; +} + +static_assert(from_xy(0b000, 0b000) == 0); +static_assert(from_xy(0b001, 0b000) == 2); +static_assert(from_xy(0b010, 0b000) == 8); +static_assert(from_xy(0b011, 0b000) == 10); +static_assert(from_xy(0b100, 0b000) == 32); +static_assert(from_xy(0b101, 0b000) == 34); +static_assert(from_xy(0b110, 0b000) == 40); +static_assert(from_xy(0b111, 0b000) == 42); + +static_assert(from_xy(0b000, 0b001) == 1); +static_assert(from_xy(0b000, 0b010) == 4); +static_assert(from_xy(0b000, 0b011) == 5); +static_assert(from_xy(0b000, 0b100) == 16); +static_assert(from_xy(0b000, 0b101) == 17); +static_assert(from_xy(0b000, 0b110) == 20); +static_assert(from_xy(0b000, 0b111) == 21); + +template +void texture(T * dst, const T * src, const uint32_t width, const uint32_t height) +{ + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + uint32_t twiddle_ix = from_xy(x, y); + T value = src[y * width + x]; + dst[twiddle_ix] = value; + } + } +} + +}