| 9120e874 Andy McCall |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
#include "../src/sprite_trace.h" |
| 11 |
#include "support/harness.h" |
| 12 |
|
| 13 |
#include <stdio.h> |
| 14 |
#include <string.h> |
| 15 |
|
| 16 |
#define W8 0 |
| 17 |
#define W16 1 |
| 18 |
#define W32 2 |
| 19 |
#define W64 3 |
| 20 |
|
| 21 |
static uint8_t attrs[SPRITE_TRACE_SLOTS][8]; |
| 22 |
|
| 23 |
static void |
| 24 |
set_attr(int slot, uint32_t addr, int x, int y, int wcode, int hcode, int z, int pal) |
| 25 |
{ |
| 26 |
uint8_t *a = attrs[slot]; |
| 27 |
a[0] = (uint8_t)((addr >> 5) & 0xff); |
| 28 |
a[1] = (uint8_t)((addr >> 13) & 0x0f); |
| 29 |
a[2] = (uint8_t)(x & 0xff); |
| 30 |
a[3] = (uint8_t)((x >> 8) & 3); |
| 31 |
a[4] = (uint8_t)(y & 0xff); |
| 32 |
a[5] = (uint8_t)((y >> 8) & 3); |
| 33 |
a[6] = (uint8_t)((z & 3) << 2); |
| 34 |
a[7] = (uint8_t)(((hcode & 3) << 6) | ((wcode & 3) << 4) | (pal & 0x0f)); |
| 35 |
} |
| 36 |
|
| 37 |
static void |
| 38 |
clear_attrs(void) |
| 39 |
{ |
| 40 |
memset(attrs, 0, sizeof(attrs)); |
| 41 |
} |
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
static void |
| 46 |
write_slot(int slot, uint16_t line, uint32_t cycle, uint32_t addr, int x, int y, int wcode, |
| 47 |
int hcode, int z, int pal) |
| 48 |
{ |
| 49 |
uint8_t old[8]; |
| 50 |
memcpy(old, attrs[slot], 8); |
| 51 |
set_attr(slot, addr, x, y, wcode, hcode, z, pal); |
| 52 |
for (int b = 0; b < 8; b++) { |
| 53 |
sprite_trace_note_write((uint8_t)slot, (uint8_t)b, old[b], attrs[slot][b], line, |
| 54 |
cycle + (uint32_t)b * 5); |
| 55 |
} |
| 56 |
} |
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
static void |
| 62 |
end_line(uint16_t line, uint32_t demand, uint16_t drawn, uint16_t dropped) |
| 63 |
{ |
| 64 |
sprite_line_stat_t st; |
| 65 |
memset(&st, 0, sizeof(st)); |
| 66 |
const bool over = demand > SPRITE_TRACE_LINE_BUDGET; |
| 67 |
st.budget_used = (uint16_t)(over ? SPRITE_TRACE_LINE_BUDGET : demand); |
| 68 |
st.demand = demand; |
| 69 |
st.evaluated = SPRITE_TRACE_SLOTS; |
| 70 |
st.drawn = drawn; |
| 71 |
st.dropped = dropped; |
| 72 |
st.cut_slot = over ? 0 : SPRITE_TRACE_NO_LINE; |
| 73 |
st.exhausted = over; |
| 74 |
sprite_trace_line_end(line, &st); |
| 75 |
} |
| 76 |
|
| 77 |
static void |
| 78 |
render_line_with(uint16_t line, uint32_t cycle, int drawing_slot, uint16_t budget) |
| 79 |
{ |
| 80 |
sprite_trace_line_begin(line, cycle); |
| 81 |
uint16_t drawn = 0; |
| 82 |
for (int slot = 0; slot < SPRITE_TRACE_SLOTS; slot++) { |
| 83 |
uint8_t flags = SPRITE_TRACE_EVALUATED; |
| 84 |
if (slot == drawing_slot) { |
| 85 |
flags |= SPRITE_TRACE_ONSCREEN | SPRITE_TRACE_DREW; |
| 86 |
drawn++; |
| 87 |
} |
| 88 |
sprite_trace_note_slot(line, (uint8_t)slot, attrs[slot], |
| 89 |
(uint16_t)(slot == drawing_slot ? budget : 1), flags); |
| 90 |
} |
| 91 |
end_line(line, budget, drawn, 0); |
| 92 |
} |
| 93 |
|
| 94 |
static void |
| 95 |
test_disabled_records_nothing(void) |
| 96 |
{ |
| 97 |
sprite_trace_set_enabled(false); |
| 98 |
check(sprite_trace_last_frame() == NULL, "no frame while tracing is off"); |
| 99 |
check(sprite_trace_memory_usage() == 0, "no memory held while tracing is off"); |
| 100 |
} |
| 101 |
|
| 102 |
static void |
| 103 |
test_a_static_sprite_is_one_effective_sprite(void) |
| 104 |
{ |
| 105 |
clear_attrs(); |
| 106 |
check(sprite_trace_set_enabled(true), "tracing enables"); |
| 107 |
|
| 108 |
set_attr(0, 0x4000, 100, 10, W16, W16, 1, 0); |
| 109 |
sprite_trace_frame_advance(1, 0, attrs); |
| 110 |
for (uint16_t y = 0; y < 40; y++) { |
| 111 |
render_line_with(y, y * 100u, (y >= 10 && y < 26) ? 0 : -1, 40); |
| 112 |
} |
| 113 |
sprite_trace_frame_advance(2, 4000, attrs); |
| 114 |
|
| 115 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 116 |
check(f != NULL, "a completed frame is published"); |
| 117 |
if (!f) { |
| 118 |
return; |
| 119 |
} |
| 120 |
check_eq(f->summary.frame, 1u, "the published frame is the one that just ended"); |
| 121 |
check_eq(f->summary.effective_sprites, 1u, "one sprite drew"); |
| 122 |
check_eq(f->summary.slots_used, 1u, "one slot drew"); |
| 123 |
check_eq(f->summary.slots_multiplexed, 0u, "nothing was multiplexed"); |
| 124 |
check_eq(f->summary.phantom_writes, 0u, "no writes were wasted"); |
| 125 |
check(!f->summary.multiplexing, "multiplexing is not reported"); |
| 126 |
|
| 127 |
|
| 128 |
|
| 129 |
uint16_t drew = 0; |
| 130 |
for (uint16_t i = 0; i < f->gen_count; i++) { |
| 131 |
if (f->gens[i].flags & SPRITE_GEN_DREW) { |
| 132 |
drew++; |
| 133 |
check_eq(f->gens[i].lines_drawn, 16u, "it drew on all 16 of its lines"); |
| 134 |
check_eq(f->gens[i].first_line, 10u, "first line matches its Y"); |
| 135 |
check_eq(f->gens[i].last_line, 25u, "last line matches its Y plus height"); |
| 136 |
} |
| 137 |
} |
| 138 |
check_eq(drew, 1u, "exactly one generation drew"); |
| 139 |
} |
| 140 |
|
| 141 |
static void |
| 142 |
test_one_slot_reused_counts_as_two_sprites(void) |
| 143 |
{ |
| 144 |
clear_attrs(); |
| 145 |
sprite_trace_set_enabled(false); |
| 146 |
check(sprite_trace_set_enabled(true), "tracing re-enables"); |
| 147 |
|
| 148 |
|
| 149 |
|
| 150 |
set_attr(0, 0x4000, 10, 0, W64, W64, 1, 0); |
| 151 |
sprite_trace_frame_advance(1, 0, attrs); |
| 152 |
|
| 153 |
for (uint16_t y = 0; y < 64; y++) { |
| 154 |
render_line_with(y, y * 100u, 0, 200); |
| 155 |
} |
| 156 |
write_slot(0, 64, 6400, 0x8000, 20, 64, W64, W64, 1, 0); |
| 157 |
for (uint16_t y = 64; y < 128; y++) { |
| 158 |
render_line_with(y, y * 100u, 0, 200); |
| 159 |
} |
| 160 |
sprite_trace_frame_advance(2, 12800, attrs); |
| 161 |
|
| 162 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 163 |
check(f != NULL, "the multiplexed frame is published"); |
| 164 |
if (!f) { |
| 165 |
return; |
| 166 |
} |
| 167 |
check_eq(f->summary.effective_sprites, 2u, "one slot showed two sprites"); |
| 168 |
check_eq(f->summary.slots_used, 1u, "still only one hardware slot"); |
| 169 |
check_eq(f->summary.slots_multiplexed, 1u, "that slot is reported as multiplexed"); |
| 170 |
check_eq(f->summary.max_gens_in_slot, 2u, "two values were held"); |
| 171 |
check(f->summary.multiplexing, "multiplexing is reported"); |
| 172 |
check_eq(f->summary.phantom_writes, 0u, "both values were displayed"); |
| 173 |
check_eq(f->summary.late_writes, 0u, "neither write was late"); |
| 174 |
|
| 175 |
|
| 176 |
|
| 177 |
uint16_t top = f->line_gen[(size_t)32 * SPRITE_TRACE_SLOTS + 0]; |
| 178 |
uint16_t bottom = f->line_gen[(size_t)96 * SPRITE_TRACE_SLOTS + 0]; |
| 179 |
check(top != bottom, "the slot maps to different values above and below the split"); |
| 180 |
if (top < f->gen_count && bottom < f->gen_count) { |
| 181 |
check_eq(f->gens[top].address, 0x4000u, "the top band is sprite A"); |
| 182 |
check_eq(f->gens[bottom].address, 0x8000u, "the bottom band is sprite B"); |
| 183 |
check_eq(f->gens[bottom].index_in_slot, 1u, "sprite B is the slot's second value"); |
| 184 |
|
| 185 |
|
| 186 |
|
| 187 |
check_eq(f->gens[bottom].writes, 3u, "three of the eight stores changed anything"); |
| 188 |
check_eq(f->summary.mutations, 8u, "all eight were still paid for"); |
| 189 |
check_eq(f->gens[bottom].write_line, 64u, "it was stamped with the line it completed on"); |
| 190 |
} |
| 191 |
} |
| 192 |
|
| 193 |
static void |
| 194 |
test_a_write_that_never_draws_is_phantom(void) |
| 195 |
{ |
| 196 |
clear_attrs(); |
| 197 |
sprite_trace_set_enabled(false); |
| 198 |
check(sprite_trace_set_enabled(true), "tracing re-enables"); |
| 199 |
|
| 200 |
sprite_trace_frame_advance(1, 0, attrs); |
| 201 |
for (uint16_t y = 0; y < 20; y++) { |
| 202 |
render_line_with(y, y * 100u, -1, 20); |
| 203 |
} |
| 204 |
|
| 205 |
|
| 206 |
write_slot(3, 20, 2000, 0x6000, 40, 400, W16, W16, 1, 0); |
| 207 |
for (uint16_t y = 20; y < 40; y++) { |
| 208 |
render_line_with(y, y * 100u, -1, 20); |
| 209 |
} |
| 210 |
sprite_trace_frame_advance(2, 4000, attrs); |
| 211 |
|
| 212 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 213 |
check(f != NULL, "the frame is published"); |
| 214 |
if (!f) { |
| 215 |
return; |
| 216 |
} |
| 217 |
check_eq(f->summary.effective_sprites, 0u, "nothing drew"); |
| 218 |
check_eq(f->summary.phantom_writes, 1u, "the wasted write is counted once"); |
| 219 |
check_eq(f->summary.mutations, 8u, "all eight stores were seen"); |
| 220 |
} |
| 221 |
|
| 222 |
static void |
| 223 |
test_a_write_after_its_own_y_span_is_late(void) |
| 224 |
{ |
| 225 |
clear_attrs(); |
| 226 |
sprite_trace_set_enabled(false); |
| 227 |
check(sprite_trace_set_enabled(true), "tracing re-enables"); |
| 228 |
|
| 229 |
sprite_trace_frame_advance(1, 0, attrs); |
| 230 |
for (uint16_t y = 0; y < 300; y++) { |
| 231 |
render_line_with(y, y * 100u, -1, 20); |
| 232 |
} |
| 233 |
|
| 234 |
|
| 235 |
write_slot(5, 300, 30000, 0x6000, 40, 10, W16, W16, 1, 0); |
| 236 |
for (uint16_t y = 300; y < 320; y++) { |
| 237 |
render_line_with(y, y * 100u, -1, 20); |
| 238 |
} |
| 239 |
sprite_trace_frame_advance(2, 32000, attrs); |
| 240 |
|
| 241 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 242 |
check(f != NULL, "the frame is published"); |
| 243 |
if (!f) { |
| 244 |
return; |
| 245 |
} |
| 246 |
check_eq(f->summary.late_writes, 1u, "the late write is identified"); |
| 247 |
|
| 248 |
bool flagged = false; |
| 249 |
for (uint16_t i = 0; i < f->gen_count; i++) { |
| 250 |
if (f->gens[i].slot == 5 && (f->gens[i].flags & SPRITE_GEN_LATE)) { |
| 251 |
flagged = true; |
| 252 |
} |
| 253 |
} |
| 254 |
check(flagged, "the generation itself carries the late flag"); |
| 255 |
} |
| 256 |
|
| 257 |
static void |
| 258 |
test_budget_overrun_is_reported(void) |
| 259 |
{ |
| 260 |
clear_attrs(); |
| 261 |
sprite_trace_set_enabled(false); |
| 262 |
check(sprite_trace_set_enabled(true), "tracing re-enables"); |
| 263 |
|
| 264 |
sprite_trace_frame_advance(1, 0, attrs); |
| 265 |
for (uint16_t y = 0; y < 10; y++) { |
| 266 |
|
| 267 |
render_line_with(y, y * 100u, 0, (uint16_t)(y == 4 ? 1500 : 300)); |
| 268 |
} |
| 269 |
sprite_trace_frame_advance(2, 1000, attrs); |
| 270 |
|
| 271 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 272 |
check(f != NULL, "the frame is published"); |
| 273 |
if (!f) { |
| 274 |
return; |
| 275 |
} |
| 276 |
check_eq(f->summary.lines_exhausted, 1u, "one scanline ran out of render time"); |
| 277 |
check_eq(f->summary.peak_line, 4u, "the peak is the overloaded line"); |
| 278 |
check_eq(f->summary.peak_demand, 1500u, "the peak records what the line wanted"); |
| 279 |
check_eq(f->summary.peak_budget, (uint32_t)SPRITE_TRACE_LINE_BUDGET, |
| 280 |
"but only the ceiling was actually spent"); |
| 281 |
check(f->lines[4].exhausted, "the line is marked exhausted"); |
| 282 |
check(!f->lines[3].exhausted, "a line within budget is not"); |
| 283 |
} |
| 284 |
|
| 285 |
static void |
| 286 |
test_writes_are_costed_in_bursts(void) |
| 287 |
{ |
| 288 |
clear_attrs(); |
| 289 |
sprite_trace_set_enabled(false); |
| 290 |
check(sprite_trace_set_enabled(true), "tracing re-enables"); |
| 291 |
sprite_trace_set_burst_gap(128); |
| 292 |
|
| 293 |
sprite_trace_frame_advance(1, 0, attrs); |
| 294 |
sprite_trace_line_begin(0, 0); |
| 295 |
|
| 296 |
|
| 297 |
for (uint32_t i = 0; i < 5; i++) { |
| 298 |
sprite_trace_note_write(0, (uint8_t)i, 0, (uint8_t)(i + 1), 0, 1000 + i * 10); |
| 299 |
} |
| 300 |
for (uint32_t i = 0; i < 5; i++) { |
| 301 |
sprite_trace_note_write(1, (uint8_t)i, 0, (uint8_t)(i + 1), 0, 5000 + i * 10); |
| 302 |
} |
| 303 |
end_line(0, 10, 0, 0); |
| 304 |
sprite_trace_frame_advance(2, 10000, attrs); |
| 305 |
|
| 306 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 307 |
check(f != NULL, "the frame is published"); |
| 308 |
if (!f) { |
| 309 |
return; |
| 310 |
} |
| 311 |
check_eq(f->summary.mutations, 10u, "every store is counted"); |
| 312 |
check_eq(f->summary.mutation_bursts, 2u, "the stores fall into two bursts"); |
| 313 |
check_eq(f->summary.mutation_cycles, 80u, "each burst spans 40 cycles"); |
| 314 |
check_eq(f->summary.burst_gap, 128u, "the gap the costing used is reported"); |
| 315 |
|
| 316 |
|
| 317 |
sprite_trace_set_burst_gap(8192); |
| 318 |
sprite_trace_frame_advance(3, 10000, attrs); |
| 319 |
sprite_trace_line_begin(0, 10000); |
| 320 |
for (uint32_t i = 0; i < 5; i++) { |
| 321 |
sprite_trace_note_write(0, (uint8_t)i, 0, (uint8_t)(i + 1), 0, 11000 + i * 10); |
| 322 |
} |
| 323 |
for (uint32_t i = 0; i < 5; i++) { |
| 324 |
sprite_trace_note_write(1, (uint8_t)i, 0, (uint8_t)(i + 1), 0, 15000 + i * 10); |
| 325 |
} |
| 326 |
end_line(0, 10, 0, 0); |
| 327 |
sprite_trace_frame_advance(4, 20000, attrs); |
| 328 |
|
| 329 |
f = sprite_trace_last_frame(); |
| 330 |
if (f) { |
| 331 |
check_eq(f->summary.mutation_bursts, 1u, "a wide gap merges the two visits"); |
| 332 |
check_eq(f->summary.mutation_cycles, 4040u, "the merged burst spans both"); |
| 333 |
} |
| 334 |
sprite_trace_set_burst_gap(128); |
| 335 |
} |
| 336 |
|
| 337 |
static void |
| 338 |
test_a_redundant_store_costs_but_does_not_split(void) |
| 339 |
{ |
| 340 |
clear_attrs(); |
| 341 |
sprite_trace_set_enabled(false); |
| 342 |
check(sprite_trace_set_enabled(true), "tracing re-enables"); |
| 343 |
|
| 344 |
set_attr(0, 0x4000, 10, 0, W16, W16, 1, 0); |
| 345 |
sprite_trace_frame_advance(1, 0, attrs); |
| 346 |
render_line_with(0, 0, 0, 40); |
| 347 |
|
| 348 |
|
| 349 |
|
| 350 |
for (int b = 0; b < 8; b++) { |
| 351 |
sprite_trace_note_write(0, (uint8_t)b, attrs[0][b], attrs[0][b], 1, 100); |
| 352 |
} |
| 353 |
for (uint16_t y = 1; y < 16; y++) { |
| 354 |
render_line_with(y, y * 100u, 0, 40); |
| 355 |
} |
| 356 |
sprite_trace_frame_advance(2, 1600, attrs); |
| 357 |
|
| 358 |
const sprite_trace_frame_t *f = sprite_trace_last_frame(); |
| 359 |
check(f != NULL, "the frame is published"); |
| 360 |
if (!f) { |
| 361 |
return; |
| 362 |
} |
| 363 |
check_eq(f->summary.mutations, 8u, "the redundant stores are still counted"); |
| 364 |
check_eq(f->summary.effective_sprites, 1u, "but no second generation was opened"); |
| 365 |
check_eq(f->summary.max_gens_in_slot, 1u, "the slot held one value throughout"); |
| 366 |
} |
| 367 |
|
| 368 |
static void |
| 369 |
test_disable_releases_the_buffers(void) |
| 370 |
{ |
| 371 |
check(sprite_trace_set_enabled(true), "tracing enables"); |
| 372 |
check(sprite_trace_memory_usage() > 0, "buffers are held while tracing"); |
| 373 |
sprite_trace_set_enabled(false); |
| 374 |
check_eq((uint32_t)sprite_trace_memory_usage(), 0u, "buffers are released when it stops"); |
| 375 |
check(sprite_trace_last_frame() == NULL, "no frame is served once disabled"); |
| 376 |
} |
| 377 |
|
| 378 |
int |
| 379 |
main(void) |
| 380 |
{ |
| 381 |
test_disabled_records_nothing(); |
| 382 |
test_a_static_sprite_is_one_effective_sprite(); |
| 383 |
test_one_slot_reused_counts_as_two_sprites(); |
| 384 |
test_a_write_that_never_draws_is_phantom(); |
| 385 |
test_a_write_after_its_own_y_span_is_late(); |
| 386 |
test_budget_overrun_is_reported(); |
| 387 |
test_writes_are_costed_in_bursts(); |
| 388 |
test_a_redundant_store_costs_but_does_not_split(); |
| 389 |
test_disable_releases_the_buffers(); |
| 390 |
return x16_test_summary("sprite_trace"); |
| 391 |
} |