11#include "absl/strings/str_format.h"
16 return std::chrono::duration_cast<std::chrono::milliseconds>(
17 std::chrono::steady_clock::now().time_since_epoch())
22#ifdef YAZE_PALETTE_DEBUG_VERBOSE
59 event.palette_id = palette_id;
60 event.color_count = palette.
size();
62 event.timestamp_ms = GetCurrentTimeMs();
66 for (
size_t i = 0; i < std::min(
size_t(3), palette.
size()); i++) {
67 auto rgb = palette[i].rgb();
68 event.sample_colors.push_back(
static_cast<uint8_t
>(rgb.x));
69 event.sample_colors.push_back(
static_cast<uint8_t
>(rgb.y));
70 event.sample_colors.push_back(
static_cast<uint8_t
>(rgb.z));
73 std::string sample_str;
76 absl::StrFormat(
"[Sample: R=%d G=%d B=%d]", event.
sample_colors[0],
80 event.message = absl::StrFormat(
"Loaded palette %d with %d colors %s",
84 LogPaletteDebugEvent(event);
88 int palette_id,
bool success,
89 const std::string& reason) {
92 event.palette_id = palette_id;
97 event.timestamp_ms = GetCurrentTimeMs();
100 success ? absl::StrFormat(
"Applied palette %d successfully", palette_id)
101 : absl::StrFormat(
"Failed to apply palette %d: %s", palette_id,
105 LogPaletteDebugEvent(event);
109 bool has_palette,
int color_count) {
112 event.color_count = color_count;
115 event.timestamp_ms = GetCurrentTimeMs();
119 ? absl::StrFormat(
"Creating texture with %d-color palette",
121 :
"WARNING: Creating texture WITHOUT palette - will use default "
125 LogPaletteDebugEvent(event);
129 SDL_Surface* surface) {
132 event.timestamp_ms = GetCurrentTimeMs();
137 event.message =
"Surface is NULL!";
139 LogPaletteDebugEvent(event);
144 if (fmt == SDL_PIXELFORMAT_UNKNOWN) {
146 event.message =
"Surface format is unknown!";
148 LogPaletteDebugEvent(event);
152 bool is_indexed = (fmt == SDL_PIXELFORMAT_INDEX8);
154 bool has_palette = (palette !=
nullptr);
155 int ncolors = has_palette ? palette->ncolors : 0;
157 event.color_count = ncolors;
161 event.message = absl::StrFormat(
162 "Surface is NOT indexed (format=0x%08X). Palette will be ignored!",
164 }
else if (!has_palette) {
166 event.message =
"Surface is indexed but has NO palette attached!";
167 }
else if (ncolors < 90) {
169 event.message = absl::StrFormat(
170 "Surface has palette with only %d colors (expected 90)", ncolors);
173 event.message = absl::StrFormat(
174 "Surface OK: indexed format, %d-color palette attached", ncolors);
177 if (palette && ncolors > 56) {
178 auto& c = palette->colors[56];
179 event.sample_colors = {c.r, c.g, c.b};
181 absl::StrFormat(
" [Color56: R=%d G=%d B=%d]", c.r, c.g, c.b);
186 LogPaletteDebugEvent(event);
194 const std::vector<SDL_Color>& palette) {
204 const std::vector<SDL_Color>& render_palette) {
233 if (x < 0 || x >= width || y < 0 || y >= height) {
240 if (!data || data_size == 0) {
244 size_t idx =
static_cast<size_t>(y * width + x);
245 if (idx >= data_size) {
261 comp.
expected_r =
static_cast<uint8_t
>(rgb.x);
262 comp.
expected_g =
static_cast<uint8_t
>(rgb.y);
263 comp.
expected_b =
static_cast<uint8_t
>(rgb.z);
286 for (
size_t i = 0; i < palette.
size(); i++) {
287 auto rgb = palette[i].rgb();
288 sum +=
static_cast<uint32_t
>(rgb.x) * (i + 1);
289 sum +=
static_cast<uint32_t
>(rgb.y) * (i + 1) * 256;
290 sum +=
static_cast<uint32_t
>(rgb.z) * (i + 1) * 65536;
296std::string PaletteDebugger::ExportToJSON()
const {
297 std::ostringstream
json;
299 for (
size_t i = 0; i <
events_.size(); i++) {
302 json <<
"\"location\":\"" << e.location <<
"\",";
303 json <<
"\"message\":\"" << e.message <<
"\",";
304 json <<
"\"level\":\""
309 json <<
"\"palette_id\":" << e.palette_id <<
",";
310 json <<
"\"color_count\":" << e.color_count;
313 if (!e.sample_colors.empty() && e.sample_colors.size() >= 3) {
314 json <<
",\"sample_rgb\":[" << (int)e.sample_colors[0] <<
","
315 << (
int)e.sample_colors[1] <<
"," << (int)e.sample_colors[2] <<
"]";
326std::string PaletteDebugger::ExportColorComparisonsJSON()
const {
327 std::ostringstream
json;
332 json <<
"\"x\":" << c.x <<
",\"y\":" << c.y <<
",";
333 json <<
"\"palette_index\":" << (int)c.palette_index <<
",";
334 json <<
"\"actual\":[" << (int)c.actual_r <<
"," << (
int)c.actual_g <<
","
335 << (int)c.actual_b <<
"],";
336 json <<
"\"expected\":[" << (int)c.expected_r <<
"," << (
int)c.expected_g
337 <<
"," << (int)c.expected_b <<
"],";
338 json <<
"\"matches\":" << (c.matches ?
"true" :
"false");
347std::string PaletteDebugger::SamplePixelJSON(
int x,
int y)
const {
349 std::ostringstream
json;
351 json <<
"\"x\":" << comp.x <<
",\"y\":" << comp.y <<
",";
352 json <<
"\"palette_index\":" << (int)comp.palette_index <<
",";
353 json <<
"\"actual\":[" << (int)comp.actual_r <<
"," << (
int)comp.actual_g
354 <<
"," << (int)comp.actual_b <<
"],";
355 json <<
"\"expected\":[" << (int)comp.expected_r <<
","
356 << (
int)comp.expected_g <<
"," << (int)comp.expected_b <<
"],";
357 json <<
"\"matches\":" << (comp.matches ?
"true" :
"false");
362std::string PaletteDebugger::ExportFullStateJSON()
const {
363 std::ostringstream
json;
367 json <<
"\"events\":" << ExportToJSON() <<
",";
370 json <<
"\"comparisons\":" << ExportColorComparisonsJSON() <<
",";
373 json <<
"\"palette\":" << ExportPaletteDataJSON() <<
",";
376 json <<
"\"timeline\":" << ExportTimelineJSON() <<
",";
379 json <<
"\"diagnostic\":\"" << GetDiagnosticSummary() <<
"\",";
382 json <<
"\"hypothesis\":\"" << GetHypothesisAnalysis() <<
"\"";
388std::string PaletteDebugger::ExportPaletteDataJSON()
const {
389 std::ostringstream
json;
394 json <<
"\"colors\":[";
398 json <<
"{\"index\":" << i <<
",\"r\":" << (int)rgb.x
399 <<
",\"g\":" << (
int)rgb.y <<
",\"b\":" << (int)rgb.z <<
"}";
405 json <<
"\"render_colors\":[";
407 bool first_render =
true;
416 json <<
"{\"index\":" << i <<
",\"r\":" <<
static_cast<int>(color.r)
417 <<
",\"g\":" <<
static_cast<int>(color.g)
418 <<
",\"b\":" <<
static_cast<int>(color.b)
419 <<
",\"a\":" <<
static_cast<int>(color.a) <<
"}";
420 first_render =
false;
427std::string PaletteDebugger::ExportTimelineJSON()
const {
428 std::ostringstream
json;
432 uint64_t first_ts = 0, last_ts = 0;
434 first_ts =
events_[0].timestamp_ms;
438 json <<
"\"start_ms\":" << first_ts <<
",";
439 json <<
"\"end_ms\":" << last_ts <<
",";
440 json <<
"\"duration_ms\":" << (last_ts - first_ts) <<
",";
441 json <<
"\"event_count\":" <<
events_.size() <<
",";
444 json <<
"\"by_location\":{";
445 std::map<std::string, int> location_counts;
446 for (
const auto& e :
events_) {
447 location_counts[e.location]++;
450 for (
const auto& [loc, count] : location_counts) {
453 json <<
"\"" << loc <<
"\":" << count;
459 int info_count = 0, warn_count = 0, error_count = 0;
460 for (
const auto& e :
events_) {
468 json <<
"\"info_count\":" << info_count <<
",";
469 json <<
"\"warning_count\":" << warn_count <<
",";
470 json <<
"\"error_count\":" << error_count;
476std::string PaletteDebugger::GetDiagnosticSummary()
const {
477 std::ostringstream summary;
480 int warnings = 0, errors = 0;
481 bool has_palette_timing_issue =
false;
482 bool has_missing_palette =
false;
483 bool has_color_mismatch =
false;
485 for (
const auto& e :
events_) {
492 if (e.message.find(
"WITHOUT palette") != std::string::npos) {
493 has_missing_palette =
true;
495 if (e.message.find(
"only") != std::string::npos &&
496 e.message.find(
"colors") != std::string::npos) {
497 has_palette_timing_issue =
true;
504 has_color_mismatch =
true;
509 summary <<
"Events: " <<
events_.size() <<
", Warnings: " << warnings
510 <<
", Errors: " << errors <<
". ";
512 if (has_missing_palette) {
513 summary <<
"ISSUE: Texture created without palette. ";
515 if (has_palette_timing_issue) {
516 summary <<
"ISSUE: Palette may not be fully loaded. ";
518 if (has_color_mismatch) {
519 summary <<
"ISSUE: Color mismatch detected between expected and actual. ";
521 if (!has_missing_palette && !has_palette_timing_issue &&
522 !has_color_mismatch) {
523 summary <<
"No obvious issues detected. ";
526 return summary.str();
529std::string PaletteDebugger::GetHypothesisAnalysis()
const {
530 std::ostringstream analysis;
533 bool texture_before_palette =
false;
534 int last_palette_load_seq = -1;
535 int first_texture_create_seq = INT_MAX;
537 for (
const auto& e :
events_) {
538 if (e.message.find(
"Loaded palette") != std::string::npos) {
539 last_palette_load_seq =
540 std::max(last_palette_load_seq, e.sequence_number);
542 if (e.message.find(
"Creating texture") != std::string::npos) {
543 first_texture_create_seq =
544 std::min(first_texture_create_seq, e.sequence_number);
548 if (first_texture_create_seq < last_palette_load_seq) {
549 texture_before_palette =
true;
550 analysis <<
"TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
551 << first_texture_create_seq <<
") before palette loaded (seq "
552 << last_palette_load_seq <<
"). ";
553 analysis <<
"FIX: Ensure palette is applied to surface BEFORE queuing "
554 "texture creation. ";
555 }
else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
556 analysis <<
"Timing appears correct: palette loaded (seq "
557 << last_palette_load_seq <<
") before texture created (seq "
558 << first_texture_create_seq <<
"). ";
562 bool wrong_palette_group =
false;
563 for (
const auto& e :
events_) {
564 if (e.color_count > 0 && e.color_count < 90) {
565 wrong_palette_group =
true;
566 analysis <<
"PALETTE GROUP ISSUE: Only " << e.color_count
567 <<
" colors (expected 90). Check palette group selection. ";
573 for (
const auto& e :
events_) {
574 if (e.message.find(
"NOT indexed") != std::string::npos) {
575 analysis <<
"FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
580 if (analysis.str().empty()) {
581 analysis <<
"No hypothesis conditions detected. Render pipeline may be "
582 "correct. Check actual colors vs expected in comparisons. ";
585 return analysis.str();
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
SDL_Surface * surface() const
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void SetCurrentBitmap(gfx::Bitmap *bitmap)
void LogTextureCreation(const std::string &location, bool has_palette, int color_count)
uint32_t ComputePaletteChecksum(const gfx::SnesPalette &palette) const
void AddEvent(const PaletteDebugEvent &event)
std::vector< SDL_Color > current_render_palette_
void LogPaletteLoad(const std::string &location, int palette_id, const gfx::SnesPalette &palette)
static PaletteDebugger & Get()
gfx::SnesPalette current_palette_
void LogPaletteApplication(const std::string &location, int palette_id, bool success, const std::string &reason="")
gfx::Bitmap * current_bitmap_
void SetCurrentPalette(const gfx::SnesPalette &palette)
std::vector< PaletteDebugEvent > events_
std::vector< ColorComparison > comparisons_
void SetCurrentPresentation(gfx::Bitmap *bitmap, const gfx::SnesPalette &palette, const std::vector< SDL_Color > &render_palette)
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
void ClearCurrentBitmapIf(const gfx::Bitmap *bitmap)
void SetCurrentRenderPalette(const std::vector< SDL_Color > &palette)
ColorComparison SamplePixelAt(int x, int y) const
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
uint64_t GetCurrentTimeMs()
void LogPaletteDebugEvent(const yaze::zelda3::PaletteDebugEvent &event)
bool ShouldLogPaletteInfo()
Zelda 3 specific classes and functions.
SDL2/SDL3 compatibility layer.
std::vector< uint8_t > sample_colors