diff --git a/dreamcast2/example/example.mk b/dreamcast2/example/example.mk index c848b71..a16fdc2 100644 --- a/dreamcast2/example/example.mk +++ b/dreamcast2/example/example.mk @@ -39,6 +39,12 @@ TRIANGLE_TA_FULLSCREEN_OBJ = \ example/triangle_ta_fullscreen.elf: LDSCRIPT = $(LIB)/main.lds example/triangle_ta_fullscreen.elf: $(START_OBJ) $(TRIANGLE_TA_FULLSCREEN_OBJ) +TRIANGLE_TA_LIST_CONT_OBJ = \ + example/triangle_ta_list_cont.o + +example/triangle_ta_list_cont.elf: LDSCRIPT = $(LIB)/main.lds +example/triangle_ta_list_cont.elf: $(START_OBJ) $(TRIANGLE_TA_LIST_CONT_OBJ) + CUBE_TA_FULLSCREEN_TEXTURED_OBJ = \ holly/core/region_array.o \ example/cube_ta_fullscreen_textured.o @@ -66,3 +72,10 @@ SIERPINSKI_TETRAHEDRON_FSAA_OBJ = \ example/sierpinski_tetrahedron_fsaa.elf: LDSCRIPT = $(LIB)/main.lds example/sierpinski_tetrahedron_fsaa.elf: $(START_OBJ) $(SIERPINSKI_TETRAHEDRON_FSAA_OBJ) + +SIERPINSKI_TETRAHEDRON_FSAA_YSCALER_OBJ = \ + holly/core/region_array.o \ + example/sierpinski_tetrahedron_fsaa_yscaler.o + +example/sierpinski_tetrahedron_fsaa_yscaler.elf: LDSCRIPT = $(LIB)/main.lds +example/sierpinski_tetrahedron_fsaa_yscaler.elf: $(START_OBJ) $(SIERPINSKI_TETRAHEDRON_FSAA_YSCALER_OBJ) diff --git a/dreamcast2/example/sierpinski_tetrahedron_fsaa_yscaler.cpp b/dreamcast2/example/sierpinski_tetrahedron_fsaa_yscaler.cpp new file mode 100644 index 0000000..afe1872 --- /dev/null +++ b/dreamcast2/example/sierpinski_tetrahedron_fsaa_yscaler.cpp @@ -0,0 +1,543 @@ +#include "memorymap.hpp" + +#include "holly/core/object_list_bits.hpp" +#include "holly/core/region_array.hpp" +#include "holly/core/region_array_bits.hpp" +#include "holly/core/parameter_bits.hpp" +#include "holly/core/parameter.hpp" +#include "holly/ta/global_parameter.hpp" +#include "holly/ta/vertex_parameter.hpp" +#include "holly/ta/parameter_bits.hpp" +#include "holly/holly.hpp" +#include "holly/holly_bits.hpp" + +#include "sh7091/sh7091.hpp" +#include "sh7091/sh7091_bits.hpp" +#include "sh7091/pref.hpp" +#include "sh7091/store_queue_transfer.hpp" + +#include "systembus/systembus.hpp" +#include "systembus/systembus_bits.hpp" + +// A blue +// B black +// C red +// D green +// A blue +// B black + +struct vec3 { + union { + float x; + float r; + }; + union { + float y; + float g; + }; + union { + float z; + float b; + }; +}; + +struct vertex { + vec3 position; + vec3 color; +}; + +constexpr float s = 2.5; + +static const vertex tetrahedron_vertex[] = { + {{ 0.500000 * s, -0.204124 * s, 0.288675 * s}, {0.0000, 0.0000, 1.0000}}, + {{ 0.000000 * s, -0.204124 * s, -0.577350 * s}, {0.0000, 0.0000, 0.0000}}, + {{-0.500000 * s, -0.204124 * s, 0.288675 * s}, {1.0000, 0.0000, 0.0000}}, + {{ 0.000000 * s, 0.612372 * s, 0.000000 * s}, {0.0000, 1.0000, 0.0000}}, +}; + +void transfer_background_polygon(uint32_t isp_tsp_parameter_start) +{ + using namespace holly::core::parameter; + + using parameter = isp_tsp_parameter<3>; + + volatile parameter * polygon = (volatile parameter *)&texture_memory32[isp_tsp_parameter_start]; + + polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + polygon->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + polygon->texture_control_word = 0; + + polygon->vertex[0].x = 0.0f; + polygon->vertex[0].y = 0.0f; + polygon->vertex[0].z = 0.00001f; + polygon->vertex[0].base_color = 0xff00ff; + + polygon->vertex[1].x = 32.0f; + polygon->vertex[1].y = 0.0f; + polygon->vertex[1].z = 0.00001f; + polygon->vertex[1].base_color = 0xff00ff; + + polygon->vertex[2].x = 32.0f; + polygon->vertex[2].y = 32.0f; + polygon->vertex[2].z = 0.00001f; + polygon->vertex[2].base_color = 0xff00ff; +} + +template +static inline void store_queue_destination_address(T address) { + using namespace sh7091; + using sh7091::sh7091; + + // set the store queue destination address to the TA Polygon Converter FIFO + sh7091.CCN.QACR0 = sh7091::ccn::qacr0::address(address); + sh7091.CCN.QACR1 = sh7091::ccn::qacr1::address(address); +} + +static inline uint32_t transfer_ta_global_end_of_list(uint32_t store_queue_ix) +{ + using namespace holly::ta; + using namespace holly::ta::parameter; + + // + // TA "end of list" global transfer + // + volatile global_parameter::end_of_list * end_of_list = (volatile global_parameter::end_of_list *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (global_parameter::end_of_list)); + + end_of_list->parameter_control_word = parameter_control_word::para_type::end_of_list; + + // start store queue transfer of `end_of_list` to the TA + + pref(end_of_list); + + return store_queue_ix; +} + +static inline uint32_t transfer_ta_global_polygon(uint32_t store_queue_ix) +{ + using namespace holly::core::parameter; + using namespace holly::ta; + using namespace holly::ta::parameter; + + // + // TA polygon global transfer + // + + volatile global_parameter::polygon_type_0 * polygon = (volatile global_parameter::polygon_type_0 *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (global_parameter::polygon_type_0)); + + polygon->parameter_control_word = parameter_control_word::para_type::polygon_or_modifier_volume + | parameter_control_word::list_type::opaque + | parameter_control_word::col_type::floating_color + | parameter_control_word::gouraud; + + polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::greater + | isp_tsp_instruction_word::culling_mode::cull_if_negative; + // Note that it is not possible to use + // ISP_TSP_INSTRUCTION_WORD::GOURAUD_SHADING in this isp_tsp_instruction_word, + // because `gouraud` is one of the bits overwritten by the value in + // parameter_control_word. See DCDBSysArc990907E.pdf page 200. + + polygon->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + polygon->texture_control_word = 0; + + polygon->data_size_for_sort_dma = 0; + polygon->next_address_for_sort_dma = 0; + + // start store queue transfer of `polygon` to the TA + pref(polygon); + + return store_queue_ix; +} + +static inline uint32_t transfer_ta_vertex_tetrahedron(uint32_t store_queue_ix, + const vec3& ap, const vec3& ac, + const vec3& bp, const vec3& bc, + const vec3& cp, const vec3& cc, + const vec3& dp, const vec3& dc) +{ + using namespace holly::ta; + using namespace holly::ta::parameter; + + if (ap.z <= 0 || bp.z <= 0 || cp.z <= 0 || dp.z <= 0) + return store_queue_ix; + + // + // TA polygon vertex transfer + // + + volatile vertex_parameter::polygon_type_1 * vertex = (volatile vertex_parameter::polygon_type_1 *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (vertex_parameter::polygon_type_1)) * 6; + +#define transfer_vertex(n, p, c, pcw) \ + vertex[n].parameter_control_word = pcw; \ + vertex[n].x = p.x; \ + vertex[n].y = p.y; \ + vertex[n].z = p.z; \ + vertex[n].base_color_r = c.r; \ + vertex[n].base_color_g = c.g; \ + vertex[n].base_color_b = c.b; \ + pref(&vertex[n]); + + store_queue_destination_address(ta_fifo_polygon_converter); + + transfer_vertex(0, ap, ac, parameter_control_word::para_type::vertex_parameter); + transfer_vertex(1, bp, bc, parameter_control_word::para_type::vertex_parameter); + transfer_vertex(2, cp, cc, parameter_control_word::para_type::vertex_parameter); + transfer_vertex(3, dp, dc, parameter_control_word::para_type::vertex_parameter); + transfer_vertex(4, ap, ac, parameter_control_word::para_type::vertex_parameter); + transfer_vertex(5, bp, bc, parameter_control_word::para_type::vertex_parameter | parameter_control_word::end_of_strip); + +#undef transfer_vertex + + return store_queue_ix; +} + +#define cos(n) __builtin_cosf(n) +#define sin(n) __builtin_sinf(n) + +static float theta = 0; + +static inline vec3 vertex_rotate(vec3 v, float cost, float sint) +{ + // to make the cube's appearance more interesting, rotate the vertex on two + // axes + + float x0 = v.x; + float y0 = v.y; + float z0 = v.z; + + float x1 = x0 * cost - z0 * sint; + float y1 = y0; + float z1 = x0 * sint + z0 * cost; + + float x2 = x1; + float y2 = y1 * cost - z1 * sint; + float z2 = y1 * sint + z1 * cost; + + return (vec3){x2, y2, z2}; +} + +static inline vec3 vertex_perspective_divide(vec3 v) +{ + float w = 1.0f / (v.z + 1.f); + return (vec3){v.x * w, v.y * w, w}; +} + +static int y_offset = 0; + +static inline vec3 vertex_screen_space(vec3 v) +{ + return (vec3){ + v.x * 480.f + 640.f, + v.y * 480.f + 480.f + y_offset, + v.z, + }; +} + +static uint32_t store_queue_ix = 0; +static float cost; +static float sint; + +static inline void tetrahedron(vec3 a, vec3 b, vec3 c, vec3 d) +{ + vec3 ap = vertex_screen_space( + vertex_perspective_divide( + vertex_rotate(a, cost, sint))); + + vec3 bp = vertex_screen_space( + vertex_perspective_divide( + vertex_rotate(b, cost, sint))); + + vec3 cp = vertex_screen_space( + vertex_perspective_divide( + vertex_rotate(c, cost, sint))); + + vec3 dp = vertex_screen_space( + vertex_perspective_divide( + vertex_rotate(d, cost, sint))); + + const vec3& ac = tetrahedron_vertex[0].color; + const vec3& bc = tetrahedron_vertex[1].color; + const vec3& cc = tetrahedron_vertex[2].color; + const vec3& dc = tetrahedron_vertex[3].color; + + store_queue_ix = transfer_ta_vertex_tetrahedron(store_queue_ix, + ap, ac, + bp, bc, + cp, cc, + dp, dc); +} + +static inline vec3 midpoint(const vec3& a, const vec3& b) +{ + return {(a.x + b.x) * 0.5f, + (a.y + b.y) * 0.5f, + (a.z + b.z) * 0.5f}; +} + +static void subdivide(vec3 a, vec3 b, vec3 c, vec3 d, + int depth) +{ + if (depth == 0) { + tetrahedron(a, b, c, d); + } else { + /* + B + / \ + A---C + */ + + vec3 ab = midpoint(a, b); + vec3 ac = midpoint(a, c); + vec3 ad = midpoint(a, d); + vec3 bc = midpoint(b, c); + vec3 bd = midpoint(b, d); + vec3 cd = midpoint(c, d); + + /* + b ---- + / \ \ + ab bc \ + / \ \ + a---ac---c--cd--d + */ + + + subdivide( a, ab, ac, ad, depth - 1); + subdivide(ab, b, bc, bd, depth - 1); + subdivide(ac, bc, c, cd, depth - 1); + subdivide(ad, bd, cd, d, depth - 1); + } +} + +void transfer_ta_sierpinski_tetrahedron() +{ + store_queue_destination_address(ta_fifo_polygon_converter); + + store_queue_ix = 0; + + store_queue_ix = transfer_ta_global_polygon(store_queue_ix); + + cost = cos(theta); + sint = sin(theta); + + subdivide(tetrahedron_vertex[0].position, + tetrahedron_vertex[1].position, + tetrahedron_vertex[2].position, + tetrahedron_vertex[3].position, + 6); + + store_queue_ix = transfer_ta_global_end_of_list(store_queue_ix); +} + +void main() +{ + /* + a very simple memory map: + + the ordering within texture memory is not significant, and could be + anything + */ + uint32_t framebuffer_start[2] = {0x000000, 0x12c000}; + uint32_t region_array_start = 0x258000; + uint32_t isp_tsp_parameter_start = 0x400000; + uint32_t object_list_start = 0x300000; + + const int tile_y_num = 480 / 32; + const int tile_x_num = 1280 / 32; + + using namespace holly::core; + + region_array::list_block_size list_block_size = { + .opaque = 8 * 4, + }; + + region_array::transfer(tile_x_num, + tile_y_num, + list_block_size, + region_array_start, + object_list_start); + + region_array::transfer(tile_x_num, + tile_y_num, + list_block_size, + region_array_start + tile_x_num * tile_y_num * 6 * 4, + object_list_start + tile_x_num * tile_y_num * 8 * 4); + + transfer_background_polygon(isp_tsp_parameter_start); + + sh7091::store_queue_transfer::zeroize((void *)&texture_memory32[framebuffer_start[0]], 640 * 480 * 4 * 2, 0); + + ////////////////////////////////////////////////////////////////////////////// + // configure the TA + ////////////////////////////////////////////////////////////////////////////// + + using namespace holly; + using holly::holly; + + // TA_GLOB_TILE_CLIP restricts which "object pointer blocks" are written + // to. + // + // This can also be used to implement "windowing", as long as the desired + // window size happens to be a multiple of 32 pixels. The "User Tile Clip" TA + // control parameter can also ~equivalently be used as many times as desired + // within a single TA initialization to produce an identical effect. + // + // See DCDBSysArc990907E.pdf page 183. + holly.TA_GLOB_TILE_CLIP = ta_glob_tile_clip::tile_y_num(tile_y_num - 1) + | ta_glob_tile_clip::tile_x_num(tile_x_num - 1); + + // While CORE supports arbitrary-length object lists, the TA uses "object + // pointer blocks" as a memory allocation strategy. These fixed-length blocks + // can still have infinite length via "object pointer block links". This + // mechanism is illustrated in DCDBSysArc990907E.pdf page 188. + holly.TA_ALLOC_CTRL = ta_alloc_ctrl::opb_mode::increasing_addresses + | ta_alloc_ctrl::o_opb::_8x4byte; + + // While building object lists, the TA contains an internal index (exposed as + // the read-only TA_ITP_CURRENT) for the next address that new ISP/TSP will be + // stored at. The initial value of this index is TA_ISP_BASE. + + // reserve space in ISP/TSP parameters for the background parameter + using polygon = holly::core::parameter::isp_tsp_parameter<3>; + uint32_t ta_isp_base_offset = (sizeof (polygon)) * 1; + + holly.TA_ISP_BASE = isp_tsp_parameter_start + ta_isp_base_offset; + holly.TA_ISP_LIMIT = isp_tsp_parameter_start + 0x400000; + + // Similarly, the TA also contains, for up to 600 tiles, an internal index for + // the next address that an object list entry will be stored for each + // tile. These internal indicies are partially exposed via the read-only + // TA_OL_POINTERS. + holly.TA_OL_BASE = object_list_start; + + // TA_OL_LIMIT, DCDBSysArc990907E.pdf page 385: + // + // > Because the TA may automatically store data in the address that is + // > specified by this register, it must not be used for other data. For + // > example, the address specified here must not be the same as the address + // > in the TA_ISP_BASE register. + holly.TA_OL_LIMIT = object_list_start + 0x200000 - 32; + + holly.TA_NEXT_OPB_INIT = (object_list_start + 8 * 4 * tile_y_num * tile_x_num * 2); + + ////////////////////////////////////////////////////////////////////////////// + // configure CORE + ////////////////////////////////////////////////////////////////////////////// + + // PARAM_BASE is the (texture memory-relative) address of ISP/TSP parameters. + // Anything that references an ISP/TSP parameter does so relative to this + // address (and not relative to the beginning of texture memory). + holly.PARAM_BASE = isp_tsp_parameter_start; + + // Set the offset of the background ISP/TSP parameter, relative to PARAM_BASE + // SKIP is related to the size of each vertex + uint32_t background_offset = 0; + + holly.ISP_BACKGND_T = isp_backgnd_t::tag_address(background_offset / 4) + | isp_backgnd_t::tag_offset(0) + | isp_backgnd_t::skip(1); + + holly.SCALER_CTL = scaler_ctl::horizontal_scaling_enable + | scaler_ctl::vertical_scale_factor(0x0800); + + holly.SOFTRESET = softreset::pipeline_soft_reset; + holly.SOFTRESET = 0; + + theta = 0; + + for (int i = 0; i < 1000 - 1; i++) { + using systembus::systembus; + using namespace systembus; + + ////////////////////////////////////////////////////////////////////////////// + // transfer cube to texture memory via the TA polygon converter FIFO + ////////////////////////////////////////////////////////////////////////////// + + holly.TA_OL_BASE = object_list_start; + holly.TA_LIST_INIT = ta_list_init::list_init; + volatile uint32_t init = holly.TA_LIST_INIT; + (void)init; + + y_offset = 0; + transfer_ta_sierpinski_tetrahedron(); + + while ((systembus.ISTNRM & istnrm::end_of_transferring_opaque_list) == 0); + systembus.ISTNRM = istnrm::end_of_transferring_opaque_list; + + ////////////////////////////////////////////////////////////////////////////// + // start the actual rasterization + ////////////////////////////////////////////////////////////////////////////// + + holly.FB_W_SOF1 = framebuffer_start[i & 1]; + + // REGION_BASE is the (texture memory-relative) address of the region array. + holly.REGION_BASE = region_array_start; + + // start the actual render--the rendering process begins by interpreting the + // region array + holly.STARTRENDER = 1; + + while ((systembus.ISTNRM & istnrm::end_of_render_tsp) == 0); + systembus.ISTNRM = istnrm::end_of_render_tsp + | istnrm::end_of_render_isp + | istnrm::end_of_render_video; + + ////////////////////////////////////////////////////////////////////////////// + // transfer cube to texture memory via the TA polygon converter FIFO + ////////////////////////////////////////////////////////////////////////////// + + holly.TA_OL_BASE = object_list_start + 8 * 4 * tile_y_num * tile_x_num; + holly.TA_LIST_CONT = ta_list_cont::list_cont; + volatile uint32_t cont = holly.TA_LIST_CONT; + (void)cont; + + y_offset = -480; + transfer_ta_sierpinski_tetrahedron(); + + while ((systembus.ISTNRM & istnrm::end_of_transferring_opaque_list) == 0); + systembus.ISTNRM = istnrm::end_of_transferring_opaque_list; + + ////////////////////////////////////////////////////////////////////////////// + // start the actual rasterization + ////////////////////////////////////////////////////////////////////////////// + + holly.FB_W_SOF1 = framebuffer_start[i & 1] + 640 * 240 * 4; + + // REGION_BASE is the (texture memory-relative) address of the region array. + holly.REGION_BASE = region_array_start + tile_x_num * tile_y_num * 6 * 4; + + // start the actual render--the rendering process begins by interpreting the + // region array + holly.STARTRENDER = 1; + + while ((systembus.ISTNRM & istnrm::end_of_render_tsp) == 0); + systembus.ISTNRM = istnrm::end_of_render_tsp + | istnrm::end_of_render_isp + | istnrm::end_of_render_video; + + ////////////////////////////////////////////////////////////////////////////// + // wait for vertical synchronization + ////////////////////////////////////////////////////////////////////////////// + + while ((spg_status::vsync(holly.SPG_STATUS))); + while (!(spg_status::vsync(holly.SPG_STATUS))); + + holly.FB_R_SOF1 = framebuffer_start[i & 1]; + + // next frame + + theta += 0.001f; + } + + // return from main; this will effectively jump back to the serial loader +} diff --git a/dreamcast2/example/triangle_ta_list_cont.cpp b/dreamcast2/example/triangle_ta_list_cont.cpp new file mode 100644 index 0000000..feddf35 --- /dev/null +++ b/dreamcast2/example/triangle_ta_list_cont.cpp @@ -0,0 +1,350 @@ +#include "memorymap.hpp" + +#include "holly/core/object_list_bits.hpp" +#include "holly/core/region_array.hpp" +#include "holly/core/region_array_bits.hpp" +#include "holly/core/parameter_bits.hpp" +#include "holly/core/parameter.hpp" +#include "holly/ta/global_parameter.hpp" +#include "holly/ta/vertex_parameter.hpp" +#include "holly/ta/parameter_bits.hpp" +#include "holly/holly.hpp" +#include "holly/holly_bits.hpp" + +#include "sh7091/sh7091.hpp" +#include "sh7091/pref.hpp" + +#include "systembus/systembus.hpp" +#include "systembus/systembus_bits.hpp" + +void transfer_background_polygon(uint32_t isp_tsp_parameter_start) +{ + using namespace holly::core::parameter; + + using parameter = isp_tsp_parameter<3>; + + volatile parameter * polygon = (volatile parameter *)&texture_memory32[isp_tsp_parameter_start]; + + polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + polygon->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + polygon->texture_control_word = 0; + + polygon->vertex[0].x = 0.0f; + polygon->vertex[0].y = 0.0f; + polygon->vertex[0].z = 0.00001f; + polygon->vertex[0].base_color = 0xff00ff; + + polygon->vertex[1].x = 32.0f; + polygon->vertex[1].y = 0.0f; + polygon->vertex[1].z = 0.00001f; + polygon->vertex[1].base_color = 0xff00ff; + + polygon->vertex[2].x = 32.0f; + polygon->vertex[2].y = 32.0f; + polygon->vertex[2].z = 0.00001f; + polygon->vertex[2].base_color = 0xff00ff; +} + +void transfer_ta_triangle(bool alt) +{ + using namespace sh7091; + using sh7091::sh7091; + + sh7091.CCN.QACR0 = sh7091::ccn::qacr0::address(ta_fifo_polygon_converter); + sh7091.CCN.QACR1 = sh7091::ccn::qacr1::address(ta_fifo_polygon_converter); + + uint32_t store_queue_ix = 0; + + using namespace holly::core::parameter; + using namespace holly::ta; + using namespace holly::ta::parameter; + + // + // TA polygon global transfer + // + + volatile global_parameter::polygon_type_0 * polygon = (volatile global_parameter::polygon_type_0 *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (global_parameter::polygon_type_0)); + + polygon->parameter_control_word = parameter_control_word::para_type::polygon_or_modifier_volume + | parameter_control_word::list_type::opaque + | parameter_control_word::col_type::packed_color + | parameter_control_word::gouraud; + + polygon->isp_tsp_instruction_word = isp_tsp_instruction_word::depth_compare_mode::always + | isp_tsp_instruction_word::culling_mode::no_culling; + + + polygon->tsp_instruction_word = tsp_instruction_word::src_alpha_instr::one + | tsp_instruction_word::dst_alpha_instr::zero + | tsp_instruction_word::fog_control::no_fog; + + pref(polygon); + + // + // TA polygon vertex transfer + // + + volatile vertex_parameter::polygon_type_0 * vertex = (volatile vertex_parameter::polygon_type_0 *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (vertex_parameter::polygon_type_0)) * 3; + + // bottom left + vertex[0].parameter_control_word = parameter_control_word::para_type::vertex_parameter; + vertex[0].x = 1.0f; + vertex[0].y = 29.0f; + vertex[0].z = 0.1f; + vertex[0].base_color = alt ? 0xffff00 : 0xff0000; // red + + // start store queue transfer of `vertex[0]` to the TA + pref(&vertex[0]); + + // top center + vertex[1].parameter_control_word = parameter_control_word::para_type::vertex_parameter; + vertex[1].x = 16.0f; + vertex[1].y = 3.0f; + vertex[1].z = 0.1f; + vertex[1].base_color = alt ? 0x00ffff : 0x00ff00; // green + + // start store queue transfer of `vertex[1]` to the TA + pref(&vertex[1]); + + // bottom right + vertex[2].parameter_control_word = parameter_control_word::para_type::vertex_parameter + | parameter_control_word::end_of_strip; + vertex[2].x = 31.0f; + vertex[2].y = 29.0f; + vertex[2].z = 0.1f; + vertex[2].base_color = alt ? 0xff80ff : 0x0000ff; // blue + + // start store queue transfer of `params[2]` to the TA + pref(&vertex[2]); + + // + // TA "end of list" global transfer + // + volatile global_parameter::end_of_list * end_of_list = (volatile global_parameter::end_of_list *)&store_queue[store_queue_ix]; + store_queue_ix += (sizeof (global_parameter::end_of_list)); + + end_of_list->parameter_control_word = parameter_control_word::para_type::end_of_list; + + // start store queue transfer of `end_of_list` to the TA + pref(end_of_list); +} + +void transfer_region_array(uint32_t region_array_start, + uint32_t opaque_list_pointer) +{ + using namespace holly::core::region_array; + /* + Create a minimal region array with a single entry: + - one tile at tile coordinate (0, 0) with one opaque list pointer + */ + + /* + Holly reads the region array from "32-bit" texture memory address space, + so the region array is correspondingly written from "32-bit" address space. + */ + volatile region_array_entry * region_array = (volatile region_array_entry *)&texture_memory32[region_array_start]; + + region_array[0].tile + = tile::y_position(0) + | tile::x_position(0); + region_array[0].list_pointer.opaque = list_pointer::object_list(opaque_list_pointer); + region_array[0].list_pointer.opaque_modifier_volume = list_pointer::empty; + region_array[0].list_pointer.translucent = list_pointer::empty; + region_array[0].list_pointer.translucent_modifier_volume = list_pointer::empty; + region_array[0].list_pointer.punch_through = list_pointer::empty; + + region_array[1].tile + = tile::last_region + | tile::y_position(0) + | tile::x_position(1); + region_array[1].list_pointer.opaque = list_pointer::empty; + region_array[1].list_pointer.opaque_modifier_volume = list_pointer::empty; + region_array[1].list_pointer.translucent = list_pointer::empty; + region_array[1].list_pointer.translucent_modifier_volume = list_pointer::empty; + region_array[1].list_pointer.punch_through = list_pointer::empty; + + region_array[2].tile + = tile::y_position(0) + | tile::x_position(0); + region_array[2].list_pointer.opaque = list_pointer::object_list(opaque_list_pointer + 8 * 4); + region_array[2].list_pointer.opaque_modifier_volume = list_pointer::empty; + region_array[2].list_pointer.translucent = list_pointer::empty; + region_array[2].list_pointer.translucent_modifier_volume = list_pointer::empty; + region_array[2].list_pointer.punch_through = list_pointer::empty; + + region_array[3].tile + = tile::last_region + | tile::y_position(0) + | tile::x_position(1); + region_array[3].list_pointer.opaque = list_pointer::empty; + region_array[3].list_pointer.opaque_modifier_volume = list_pointer::empty; + region_array[3].list_pointer.translucent = list_pointer::empty; + region_array[3].list_pointer.translucent_modifier_volume = list_pointer::empty; + region_array[3].list_pointer.punch_through = list_pointer::empty; +} + +void main() +{ + /* + a very simple memory map: + + the ordering within texture memory is not significant, and could be + anything + */ + uint32_t framebuffer_start = 0x200000; // intentionally the same address that the boot rom used to draw the SEGA logo + uint32_t isp_tsp_parameter_start = 0x400000; + uint32_t region_array_start = 0x500000; + uint32_t object_list_start = 0x100000; + + transfer_region_array(region_array_start, + object_list_start); + + transfer_background_polygon(isp_tsp_parameter_start); + + ////////////////////////////////////////////////////////////////////////////// + // configure the TA + ////////////////////////////////////////////////////////////////////////////// + + const int tile_y_num = 1; + const int tile_x_num = 1; + + using namespace holly; + using holly::holly; + + // TA_GLOB_TILE_CLIP restricts which "object pointer blocks" are written + // to. + // + // This can also be used to implement "windowing", as long as the desired + // window size happens to be a multiple of 32 pixels. The "User Tile Clip" TA + // control parameter can also ~equivalently be used as many times as desired + // within a single TA initialization to produce an identical effect. + // + // See DCDBSysArc990907E.pdf page 183. + holly.TA_GLOB_TILE_CLIP = ta_glob_tile_clip::tile_y_num(tile_y_num - 1) + | ta_glob_tile_clip::tile_x_num(tile_x_num - 1); + + // While CORE supports arbitrary-length object lists, the TA uses "object + // pointer blocks" as a memory allocation strategy. These fixed-length blocks + // can still have infinite length via "object pointer block links". This + // mechanism is illustrated in DCDBSysArc990907E.pdf page 188. + holly.TA_ALLOC_CTRL = ta_alloc_ctrl::opb_mode::increasing_addresses + | ta_alloc_ctrl::o_opb::_8x4byte; + + // While building object lists, the TA contains an internal index (exposed as + // the read-only TA_ITP_CURRENT) for the next address that new ISP/TSP will be + // stored at. The initial value of this index is TA_ISP_BASE. + + // reserve space in ISP/TSP parameters for the background parameter + using polygon = holly::core::parameter::isp_tsp_parameter<3>; + uint32_t ta_isp_base_offset = (sizeof (polygon)) * 1; + + holly.TA_ISP_BASE = isp_tsp_parameter_start + ta_isp_base_offset; + holly.TA_ISP_LIMIT = isp_tsp_parameter_start + 0x100000; + + // Similarly, the TA also contains, for up to 600 tiles, an internal index for + // the next address that an object list entry will be stored for each + // tile. These internal indicies are partially exposed via the read-only + // TA_OL_POINTERS. + holly.TA_OL_BASE = object_list_start; + + // TA_OL_LIMIT, DCDBSysArc990907E.pdf page 385: + // + // > Because the TA may automatically store data in the address that is + // > specified by this register, it must not be used for other data. For + // > example, the address specified here must not be the same as the address + // > in the TA_ISP_BASE register. + holly.TA_OL_LIMIT = object_list_start + 0x100000 - 32; + + holly.TA_NEXT_OPB_INIT = (object_list_start + 8 * 4 * 2 * 1); + + holly.TA_LIST_INIT = ta_list_init::list_init; + + // dummy TA_LIST_INIT read; DCDBSysArc990907E.pdf in multiple places says this + // step is required. + (void)holly.TA_LIST_INIT; + + ////////////////////////////////////////////////////////////////////////////// + // transfer triangles to texture memory via the TA polygon converter FIFO + ////////////////////////////////////////////////////////////////////////////// + + using systembus::systembus; + using namespace systembus; + + transfer_ta_triangle(true); + + while ((systembus.ISTNRM & istnrm::end_of_transferring_opaque_list) == 0); + systembus.ISTNRM = istnrm::end_of_transferring_opaque_list; + + // transfer again + holly.TA_OL_BASE = object_list_start + 8 * 4; + holly.TA_LIST_CONT = ta_list_cont::list_cont; + (void)holly.TA_LIST_CONT; + + transfer_ta_triangle(false); + + while ((systembus.ISTNRM & istnrm::end_of_transferring_opaque_list) == 0); + systembus.ISTNRM = istnrm::end_of_transferring_opaque_list; + + ////////////////////////////////////////////////////////////////////////////// + // configure CORE + ////////////////////////////////////////////////////////////////////////////// + + holly.SCALER_CTL = scaler_ctl::vertical_scale_factor(0x0800); + //holly.SCALER_CTL = scaler_ctl::vertical_scale_factor(0x0400); + + holly.SOFTRESET = 0b11; + holly.SOFTRESET = 0; + + While ((spg_status::vsync(holly.SPG_STATUS))); + while (!(spg_status::vsync(holly.SPG_STATUS))); + + // REGION_BASE is the (texture memory-relative) address of the region array. + holly.REGION_BASE = region_array_start; + + // PARAM_BASE is the (texture memory-relative) address of ISP/TSP parameters. + // Anything that references an ISP/TSP parameter does so relative to this + // address (and not relative to the beginning of texture memory). + holly.PARAM_BASE = isp_tsp_parameter_start; + + // Set the offset of the background ISP/TSP parameter, relative to PARAM_BASE + // SKIP is related to the size of each vertex + uint32_t background_offset = 0; + + holly.ISP_BACKGND_T = isp_backgnd_t::tag_address(background_offset / 4) + | isp_backgnd_t::tag_offset(0) + | isp_backgnd_t::skip(1); + + // FB_W_SOF1 is the (texture memory-relative) address of the framebuffer that + // will be written to when a tile is rendered/flushed. + holly.FB_W_SOF1 = framebuffer_start; + + // start the actual render--the rendering process begins by interpreting the + // region array + holly.STARTRENDER = 1; + + while ((systembus.ISTNRM & istnrm::end_of_render_tsp) == 0); + systembus.ISTNRM = istnrm::end_of_render_tsp + | istnrm::end_of_render_isp + | istnrm::end_of_render_video; + + holly.REGION_BASE = region_array_start + 6 * 4 * 2; + holly.FB_W_SOF1 = framebuffer_start + 640 * 32 * 4 * 2; + + holly.STARTRENDER = 1; + + holly.FB_R_SOF1 = framebuffer_start; + + while ((systembus.ISTNRM & istnrm::end_of_render_tsp) == 0); + systembus.ISTNRM = istnrm::end_of_render_tsp + | istnrm::end_of_render_isp + | istnrm::end_of_render_video; + + // return from main; this will effectively jump back to the serial loader +} diff --git a/dreamcast2/holly/core/region_array.cpp b/dreamcast2/holly/core/region_array.cpp index 8090d10..0b08f6a 100644 --- a/dreamcast2/holly/core/region_array.cpp +++ b/dreamcast2/holly/core/region_array.cpp @@ -21,36 +21,39 @@ namespace holly::core::region_array { for (int y = 0; y < tile_height; y++) { for (int x = 0; x < tile_width; x++) { - region_array[ix].tile = tile::y_position(y) + int rix = x * tile_height + y; + //int rix = y * tile_width + x; + + region_array[rix].tile = tile::y_position(y) | tile::x_position(x); if (y == (tile_height - 1) && x == (tile_width - 1)) - region_array[ix].tile |= tile::last_region; + region_array[rix].tile |= tile::last_region; - region_array[ix].list_pointer.opaque = (list_block_size.opaque == 0) ? list_pointer::empty : + region_array[rix].list_pointer.opaque = (list_block_size.opaque == 0) ? list_pointer::empty : (ol_base + (list_block_size.opaque * ix) ); - region_array[ix].list_pointer.opaque_modifier_volume = (list_block_size.opaque_modifier_volume == 0) ? list_pointer::empty : + region_array[rix].list_pointer.opaque_modifier_volume = (list_block_size.opaque_modifier_volume == 0) ? list_pointer::empty : (ol_base + num_tiles * ( list_block_size.opaque ) + (list_block_size.opaque_modifier_volume * ix) ); - region_array[ix].list_pointer.translucent = (list_block_size.translucent == 0) ? list_pointer::empty : + region_array[rix].list_pointer.translucent = (list_block_size.translucent == 0) ? list_pointer::empty : (ol_base + num_tiles * ( list_block_size.opaque + list_block_size.opaque_modifier_volume ) + (list_block_size.translucent * ix) ); - region_array[ix].list_pointer.translucent_modifier_volume = (list_block_size.translucent_modifier_volume == 0) ? list_pointer::empty : + region_array[rix].list_pointer.translucent_modifier_volume = (list_block_size.translucent_modifier_volume == 0) ? list_pointer::empty : (ol_base + num_tiles * ( list_block_size.opaque + list_block_size.opaque_modifier_volume + list_block_size.translucent ) + (list_block_size.translucent_modifier_volume * ix) ); - region_array[ix].list_pointer.punch_through = (list_block_size.punch_through == 0) ? list_pointer::empty : + region_array[rix].list_pointer.punch_through = (list_block_size.punch_through == 0) ? list_pointer::empty : (ol_base + num_tiles * ( list_block_size.opaque + list_block_size.opaque_modifier_volume + list_block_size.translucent