From 9610c428bd85d1369580fb7e522bfdd048ef749c Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 6 Dec 2023 20:57:52 +0800 Subject: [PATCH] draw a textured triangle strip This draws a nice macaw texture in a square-shaped triangle strip. The square is then rotated around the y-axis. I dealt with myriad bugs while experimenting with this, all of them entirely my fault: - macaw texture colors were incorrect because GIMP was exporting raw RGB data in gamma-corrected sRGB space, whereas the Dreamcast is in linear color space. - macaw texture colors were incorrect because I truncated color values to the least significant rather than most significant bits. - macaw rotation around the Y axis caused the macaw texture to distort, stretch and recurse in interesting and unexpected ways. This was caused by sending Z values in the wrong coordinate space (Z) contrast to what is expected by the Dreamcast (1/z). Reordering z-coordinate operations so that the reciprocal is computed last resolved this. - macaw rotation around the Y axis caused the macaw texture to warp unexpectedly, but only on real hardware. This was caused by unnecessarily negating Z coordinate values. Behavior for each of the Z-coordinate issues differed between Flycast and real Dreamcast hardware. I also did several tests related to SH4 cache behavior, particularly related to the "copy-back" mode. I verified copy-back behavior on a real dreamcast, and experimented with the operand cache write-back instruction, "ocbwb". In particular, when the `scene` buffer is access from cacheable memory, e.g: the P1 area, and CCR__CB is enabled, DMA from physical memory to the TA FIFO polygon converter will fail because the scene data has not yet been written to physical memory yet. `ocbwb` can be used to "write back" scene from the SH4 operand cache to physical memory--only the latter is visible from the CH2-DMA perspective. --- cache.cpp | 7 ++- common.mk | 3 +- holly/core.cpp | 5 +- holly/isp_tsp.h | 38 +++++++++++++- holly/ta_fifo_polygon_converter.cpp | 16 +++++- holly/ta_parameter.cpp | 75 +++++++++++++++++++++++++++- holly/ta_parameter.h | 15 ++++++ holly/texture_memory_alloc.h | 2 + macaw.data | Bin 0 -> 49152 bytes macaw.h | 5 ++ main.cpp | 26 ++++++++-- scene.cpp | 43 ++++++++++------ texture_memory_alloc.h | 12 ----- 13 files changed, 205 insertions(+), 42 deletions(-) create mode 100644 macaw.data create mode 100644 macaw.h delete mode 100644 texture_memory_alloc.h diff --git a/cache.cpp b/cache.cpp index a72b456..9f4b401 100644 --- a/cache.cpp +++ b/cache.cpp @@ -17,7 +17,12 @@ void cache_init() sh7091_oc_a[i][0] = 0; } - sh7091.CCN.CCR = CCR__ICI | CCR__ICE | CCR__OCI | CCR__OCE; + sh7091.CCN.CCR = CCR__ICI // instruction cache invalidate + | CCR__ICE // instruction cache enable + | CCR__OCI // operand cache invalidate + | CCR__OCE // operand cache enable + // | CCR__CB // enable copy-back mode for the P1 area + ; sh7091.CCN.MMUCR = 0; diff --git a/common.mk b/common.mk index 4f2de30..91e603e 100644 --- a/common.mk +++ b/common.mk @@ -57,7 +57,8 @@ MAIN_OBJ = \ holly/ta_parameter.o \ holly/ta_fifo_polygon_converter.o \ holly/core.o \ - scene.o + scene.o \ + macaw.data.o all: main.cdi diff --git a/holly/core.cpp b/holly/core.cpp index 7715260..5ef0b97 100644 --- a/holly/core.cpp +++ b/holly/core.cpp @@ -76,9 +76,8 @@ void core_start_render(int fb) int w_fb = (!(!fb)) * 0x00096000; int r_fb = (!fb) * 0x00096000; holly.FB_W_SOF1 = (offsetof (struct texture_memory_alloc, framebuffer)) + w_fb; - holly.FB_W_SOF2 = (offsetof (struct texture_memory_alloc, framebuffer)) + w_fb; - holly.FB_R_SOF1 = (offsetof (struct texture_memory_alloc, framebuffer)) + r_fb; - holly.FB_R_SOF2 = (offsetof (struct texture_memory_alloc, framebuffer)) + r_fb; + //holly.FB_R_SOF1 = (offsetof (struct texture_memory_alloc, framebuffer)) + r_fb; + holly.FB_R_SOF1 = (offsetof (struct texture_memory_alloc, framebuffer)) + w_fb; holly.STARTRENDER = 1; } diff --git a/holly/isp_tsp.h b/holly/isp_tsp.h index fb8579a..dc6339d 100644 --- a/holly/isp_tsp.h +++ b/holly/isp_tsp.h @@ -65,8 +65,19 @@ namespace tsp_instruction_word { constexpr uint32_t use_alpha = 1 << 20; constexpr uint32_t ignore_tex_alpha = 1 << 19; - // flip_uv - // clamp_uv + namespace flip_uv { + constexpr uint32_t none = 0 << 17; + constexpr uint32_t v = 1 << 17; + constexpr uint32_t u = 2 << 17; + constexpr uint32_t uv = 3 << 17; + } + + namespace clamp_uv { + constexpr uint32_t none = 0 << 15; + constexpr uint32_t v = 1 << 15; + constexpr uint32_t u = 2 << 15; + constexpr uint32_t uv = 3 << 15; + } namespace filter_mode { constexpr uint32_t point_sampled = 0b00 << 13; @@ -111,3 +122,26 @@ namespace tsp_instruction_word { constexpr uint32_t _1024 = 7 << 0; } } + +namespace texture_control_word { + constexpr uint32_t mip_mapped = 1 << 31; + constexpr uint32_t vq_compressed = 1 << 30; + + namespace pixel_format { + constexpr uint32_t _1555 = 0 << 27; + constexpr uint32_t _565 = 1 << 27; + constexpr uint32_t _4444 = 2 << 27; + constexpr uint32_t yuv422 = 3 << 27; + constexpr uint32_t bump_map = 4 << 27; + constexpr uint32_t _4bpp_palette = 5 << 27; + constexpr uint32_t _8bpp_palette = 6 << 27; + } + + constexpr uint32_t scan_order = 1 << 26; + constexpr uint32_t stride_select = 1 << 25; + + // in 8-byte units + constexpr uint32_t texture_address(uint32_t a) { + return a & 0x1fffff; + } +} diff --git a/holly/ta_fifo_polygon_converter.cpp b/holly/ta_fifo_polygon_converter.cpp index 68f0d41..fe43d3a 100644 --- a/holly/ta_fifo_polygon_converter.cpp +++ b/holly/ta_fifo_polygon_converter.cpp @@ -51,10 +51,22 @@ void ta_polygon_converter_transfer(volatile uint32_t * buf, uint32_t size) DCDBSysArc990907E's claim, it does not appear to be useful to check TE. */ //while ((sh7091.DMAC.CHCR2 & CHCR2__TE) == 0); /* 1 == all transfers are completed */ - /* start a new CH2-DMA transfer from "system memory" to "TA FIFO polygon converter" */ - // this dummy read is required on real hardware. + /* "Write back" the entire buffer to physical memory. + + This is required on real hardware if CCR__CB is enabled, and `buf` is in a + cacheable area (e.g: system memory access via 0x8c00_0000).*/ + for (uint32_t i = 0; i < size / 32; i++) { + asm volatile ("ocbwb @%0" + : // output + : "r" (&buf[(i * 32) / 4]) // input + ); + } + + // this dummy read appears to be required on real hardware. volatile uint32_t _dummy = sh7091.DMAC.CHCR2; (void)_dummy; + + /* start a new CH2-DMA transfer from "system memory" to "TA FIFO polygon converter" */ sh7091.DMAC.CHCR2 = 0; /* disable DMA channel */ sh7091.DMAC.SAR2 = reinterpret_cast(&buf[0]); /* start address, must be aligned to a CHCHR__TS-sized (32-byte) boundary */ sh7091.DMAC.DMATCR2 = DMATCR2__TRANSFER_COUNT(size / 32); /* transfer count, in CHCHR__TS-sized (32-byte) units */ diff --git a/holly/ta_parameter.cpp b/holly/ta_parameter.cpp index e6d1701..c8f92d0 100644 --- a/holly/ta_parameter.cpp +++ b/holly/ta_parameter.cpp @@ -18,6 +18,17 @@ struct vertex_polygon_type_0 { uint32_t _res2; }; +struct vertex_polygon_type_3 { + uint32_t parameter_control_word; + float x; + float y; + float z; + float u; + float v; + uint32_t base_color; + uint32_t offset_color; +}; + static_assert((sizeof (vertex_polygon_type_0)) == 32); static_assert((offsetof (struct vertex_polygon_type_0, parameter_control_word)) == 0x00); static_assert((offsetof (struct vertex_polygon_type_0, x)) == 0x04); @@ -155,7 +166,7 @@ void triangle(volatile uint32_t * buf) parameter->parameter_control_word = para_control::para_type::polygon_or_modifier_volume | para_control::list_type::opaque - | obj_control::col_type::packed_color; + | obj_control::col_type::packed_color; parameter->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always | isp_tsp_instruction_word::culling_mode::no_culling; @@ -170,6 +181,68 @@ void triangle(volatile uint32_t * buf) parameter->next_address_for_sort_dma = 0; } +void textured_vertex(volatile uint32_t * buf, + const float x, + const float y, + const float z, + const float u, + const float v, + const uint32_t base_color, + const uint32_t offset_color, + bool end_of_strip + ) +{ + volatile vertex_polygon_type_3 * parameter = reinterpret_cast(buf); + + parameter->parameter_control_word = para_control::para_type::vertex_parameter; + + if (end_of_strip) + parameter->parameter_control_word |= para_control::end_of_strip; + + parameter->x = x; + parameter->y = y; + parameter->z = z; + parameter->u = u; + parameter->v = v; + parameter->base_color = base_color; + parameter->offset_color = offset_color; +} + +void textured_triangle(volatile uint32_t * buf, + uint32_t texture_address) +{ + volatile global_polygon_type_0 * parameter = reinterpret_cast(buf); + + parameter->parameter_control_word = para_control::para_type::polygon_or_modifier_volume + | para_control::list_type::opaque + | obj_control::col_type::packed_color + | obj_control::texture; + + parameter->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + // Because a value of "0.0" is invalid for [MIP-Map] D [adjust], it must not be specified. + parameter->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog + //| tsp_instruction_word::mip_map_d_adjust(0b0100) // 1.0 (2.2 fixed-point) + //| tsp_instruction_word::filter_mode::bilinear_filter + //| tsp_instruction_word::clamp_uv::uv + //| tsp_instruction_word::flip_uv::uv + | tsp_instruction_word::texture_u_size::_128 // 128px + | tsp_instruction_word::texture_v_size::_128; // 128px + + if ((texture_address & 63) != 0) while (1); + parameter->texture_control_word = texture_control_word::pixel_format::_565 + | texture_control_word::scan_order // non-twiddled + | texture_control_word::texture_address(texture_address / 8); + + parameter->_res0 = 0; + parameter->_res1 = 0; + parameter->data_size_for_sort_dma = 0; + parameter->next_address_for_sort_dma = 0; +} + void end_of_list(volatile uint32_t * buf) { volatile global_end_of_list * parameter = reinterpret_cast(buf); diff --git a/holly/ta_parameter.h b/holly/ta_parameter.h index 213ade7..5ba5d28 100644 --- a/holly/ta_parameter.h +++ b/holly/ta_parameter.h @@ -12,4 +12,19 @@ void vertex(volatile uint32_t * buf, void triangle(volatile uint32_t * buf); +void textured_vertex(volatile uint32_t * buf, + const float x, + const float y, + const float z, + const float u, + const float v, + const uint32_t base_color, + const uint32_t offset_color, + bool end_of_strip + ); + +void textured_triangle(volatile uint32_t * buf, + uint32_t texture_address + ); + void end_of_list(volatile uint32_t * buf); diff --git a/holly/texture_memory_alloc.h b/holly/texture_memory_alloc.h index c6cc208..5325e9c 100644 --- a/holly/texture_memory_alloc.h +++ b/holly/texture_memory_alloc.h @@ -9,4 +9,6 @@ struct texture_memory_alloc { uint32_t region_array[0x00002000 / 4]; // REGION_BASE uint32_t background[0x00000040 / 4]; // ISP_BACKGND_T uint32_t framebuffer[2][0x00096000 / 4]; // FB_R_SOF1 / FB_W_SOF1 + uint32_t _res1[ 0x20 / 4]; // (re-align texture to a 64-byte boundary) + uint16_t texture[128 * 128 * 2 / 2]; // texture_control_word::texture_address }; diff --git a/macaw.data b/macaw.data new file mode 100644 index 0000000000000000000000000000000000000000..d0fcdf9478e7fd69dbbbd5b715be0b6aaa6489c8 GIT binary patch literal 49152 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�si>OjUyle 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 I7sCDj0PRyK5dZ)H literal 0 HcmV?d00001 diff --git a/macaw.h b/macaw.h new file mode 100644 index 0000000..b1b58c9 --- /dev/null +++ b/macaw.h @@ -0,0 +1,5 @@ +#include + +extern uint32_t _binary_macaw_data_start __asm("_binary_macaw_data_start"); +extern uint32_t _binary_macaw_data_end __asm("_binary_macaw_data_end"); +extern uint32_t _binary_macaw_data_size __asm("_binary_macaw_data_size"); diff --git a/main.cpp b/main.cpp index 4dc3f3d..7bea9b3 100644 --- a/main.cpp +++ b/main.cpp @@ -10,12 +10,16 @@ #include "holly/ta_fifo_polygon_converter.h" #include "systembus.h" +#include "holly/texture_memory_alloc.h" + #include "cache.h" #include "load.h" #include "vga.h" #include "rgb.h" #include "scene.h" +#include "macaw.h" + extern uint32_t __bss_link_start __asm("__bss_link_start"); extern uint32_t __bss_link_end __asm("__bss_link_end"); @@ -87,12 +91,25 @@ void main() } } + volatile texture_memory_alloc * mem = reinterpret_cast(0xa400'0000); + + volatile uint8_t * macaw = reinterpret_cast(&_binary_macaw_data_start); + uint32_t macaw_size = reinterpret_cast(&_binary_macaw_data_size); + for (uint32_t px = 0; px < macaw_size / 3; px++) { + uint8_t r = macaw[px * 3 + 0]; + uint8_t g = macaw[px * 3 + 1]; + uint8_t b = macaw[px * 3 + 2]; + + 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; holly.SOFTRESET = 0; - system.LMMODE0 = 1; - system.LMMODE1 = 1; + //system.LMMODE0 = 1; // texture memory through TA FIFO + //system.LMMODE1 = 1; // texture memory through TA FIFO (mirror) v_sync_out(); v_sync_in(); @@ -100,8 +117,6 @@ void main() core_init(); core_init_texture_memory(); - int frame = 0; - // the address of `scene` must be a multiple of 32 bytes // this is mandatory for ch2-dma to the ta fifo polygon converter uint32_t * scene = align_32byte(_scene); @@ -110,6 +125,9 @@ void main() while(1); } + int frame = 0; + int ix = 0; + while (true) { v_sync_out(); v_sync_in(); diff --git a/scene.cpp b/scene.cpp index ed642ff..9a77c0a 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,9 +1,12 @@ #include +#include #include "holly/ta_parameter.h" +#include "holly/texture_memory_alloc.h" + /* - 0,-.5 + -0.5,-0.5 0.5,-0.5 | --- -0.5,0.5 | 0.5,0.5 @@ -13,24 +16,28 @@ struct triangle { float x; float y; float z; + float u; + float v; uint32_t color; }; const struct triangle scene_triangle[4] = { - { -0.5f, 0.5f, 1/10.f, 0x00000000}, // the first two base colors in a - { -0.5f, -0.5f, 1/10.f, 0x00000000}, // triangle strip are ignored - { 0.5f, 0.5f, 1/10.f, 0xffff00ff}, - { 0.5f, -0.5f, 1/10.f, 0xffffff00}, + { -0.5f, 0.5f, 0.f, 0.f , 128.f/128.f, 0x00000000}, // the first two base colors in a + { -0.5f, -0.5f, 0.f, 0.f , 0.f , 0x00000000}, // triangle strip are ignored + { 0.5f, 0.5f, 0.f, 128.f/128.f, 128.f/128.f, 0xffff00ff}, + { 0.5f, -0.5f, 0.f, 128.f/128.f, 0.f , 0xffffff00}, }; static float theta = 0; -constexpr float one_degree = 0.01745329f; +constexpr float one_degree = 0.01745329f / 2.f; uint32_t scene_transform(volatile uint32_t * scene) { uint32_t ix = 0; - triangle(&scene[(32 * ix) / 4]); + uint32_t address = (offsetof (struct texture_memory_alloc, texture)); + textured_triangle(&scene[(32 * ix) / 4], + address); ix++; for (int i = 0; i < 4; i++) { @@ -38,22 +45,26 @@ uint32_t scene_transform(volatile uint32_t * scene) float x = scene_triangle[i].x; float y = scene_triangle[i].y; + float z = scene_triangle[i].z; float x1; - x1 = x * __builtin_cosf(theta) - y * __builtin_sinf(theta); - y = x * __builtin_sinf(theta) + y * __builtin_cosf(theta); - x = 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; - vertex(&scene[(32 * ix) / 4], - x, // x - y, // y - scene_triangle[i].z, // z - scene_triangle[i].color, // base_color - end_of_strip); + textured_vertex(&scene[(32 * ix) / 4], + x, // x + y, // y + 1.f / (z + 10.f), // z + scene_triangle[i].u, // u + scene_triangle[i].v, // v + scene_triangle[i].color, // base_color + 0, // offset_color + end_of_strip); ix++; } diff --git a/texture_memory_alloc.h b/texture_memory_alloc.h deleted file mode 100644 index 5118577..0000000 --- a/texture_memory_alloc.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -struct __attribute__((__packed__)) texture_memory_alloc { - uint32_t isp_tsp_parameters[0x00100000 / 4]; // TA_ISP_BASE / PARAM_BASE (the actual objects) - uint32_t object_list[0x00100000 / 4]; // TA_OL_BASE (contains object pointer blocks) - uint32_t _res0[ 0x20 / 4]; // (the TA may clobber 4 bytes starting at TA_OL_LIMIT) - uint32_t region_array[0x00002000 / 4]; // REGION_BASE - uint32_t background[0x00000020 / 4]; // ISP_BACKGND_T - uint32_t framebuffer[2][0x00096000 / 4]; // FB_R_SOF1 / FB_W_SOF1 -};