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) {
219 if (x < 0 || x >= width || y < 0 || y >= height) {
226 if (!data || data_size == 0) {
230 size_t idx =
static_cast<size_t>(y * width + x);
231 if (idx >= data_size) {
247 comp.
expected_r =
static_cast<uint8_t
>(rgb.x);
248 comp.
expected_g =
static_cast<uint8_t
>(rgb.y);
249 comp.
expected_b =
static_cast<uint8_t
>(rgb.z);
272 for (
size_t i = 0; i < palette.
size(); i++) {
273 auto rgb = palette[i].rgb();
274 sum +=
static_cast<uint32_t
>(rgb.x) * (i + 1);
275 sum +=
static_cast<uint32_t
>(rgb.y) * (i + 1) * 256;
276 sum +=
static_cast<uint32_t
>(rgb.z) * (i + 1) * 65536;
282std::string PaletteDebugger::ExportToJSON()
const {
283 std::ostringstream
json;
285 for (
size_t i = 0; i <
events_.size(); i++) {
288 json <<
"\"location\":\"" << e.location <<
"\",";
289 json <<
"\"message\":\"" << e.message <<
"\",";
290 json <<
"\"level\":\""
295 json <<
"\"palette_id\":" << e.palette_id <<
",";
296 json <<
"\"color_count\":" << e.color_count;
299 if (!e.sample_colors.empty() && e.sample_colors.size() >= 3) {
300 json <<
",\"sample_rgb\":[" << (int)e.sample_colors[0] <<
","
301 << (
int)e.sample_colors[1] <<
"," << (int)e.sample_colors[2] <<
"]";
312std::string PaletteDebugger::ExportColorComparisonsJSON()
const {
313 std::ostringstream
json;
318 json <<
"\"x\":" << c.x <<
",\"y\":" << c.y <<
",";
319 json <<
"\"palette_index\":" << (int)c.palette_index <<
",";
320 json <<
"\"actual\":[" << (int)c.actual_r <<
"," << (
int)c.actual_g <<
","
321 << (int)c.actual_b <<
"],";
322 json <<
"\"expected\":[" << (int)c.expected_r <<
"," << (
int)c.expected_g
323 <<
"," << (int)c.expected_b <<
"],";
324 json <<
"\"matches\":" << (c.matches ?
"true" :
"false");
333std::string PaletteDebugger::SamplePixelJSON(
int x,
int y)
const {
335 std::ostringstream
json;
337 json <<
"\"x\":" << comp.x <<
",\"y\":" << comp.y <<
",";
338 json <<
"\"palette_index\":" << (int)comp.palette_index <<
",";
339 json <<
"\"actual\":[" << (int)comp.actual_r <<
"," << (
int)comp.actual_g
340 <<
"," << (int)comp.actual_b <<
"],";
341 json <<
"\"expected\":[" << (int)comp.expected_r <<
","
342 << (
int)comp.expected_g <<
"," << (int)comp.expected_b <<
"],";
343 json <<
"\"matches\":" << (comp.matches ?
"true" :
"false");
348std::string PaletteDebugger::ExportFullStateJSON()
const {
349 std::ostringstream
json;
353 json <<
"\"events\":" << ExportToJSON() <<
",";
356 json <<
"\"comparisons\":" << ExportColorComparisonsJSON() <<
",";
359 json <<
"\"palette\":" << ExportPaletteDataJSON() <<
",";
362 json <<
"\"timeline\":" << ExportTimelineJSON() <<
",";
365 json <<
"\"diagnostic\":\"" << GetDiagnosticSummary() <<
"\",";
368 json <<
"\"hypothesis\":\"" << GetHypothesisAnalysis() <<
"\"";
374std::string PaletteDebugger::ExportPaletteDataJSON()
const {
375 std::ostringstream
json;
380 json <<
"\"colors\":[";
384 json <<
"{\"index\":" << i <<
",\"r\":" << (int)rgb.x
385 <<
",\"g\":" << (
int)rgb.y <<
",\"b\":" << (int)rgb.z <<
"}";
391 json <<
"\"render_colors\":[";
393 bool first_render =
true;
402 json <<
"{\"index\":" << i <<
",\"r\":" <<
static_cast<int>(color.r)
403 <<
",\"g\":" <<
static_cast<int>(color.g)
404 <<
",\"b\":" <<
static_cast<int>(color.b)
405 <<
",\"a\":" <<
static_cast<int>(color.a) <<
"}";
406 first_render =
false;
413std::string PaletteDebugger::ExportTimelineJSON()
const {
414 std::ostringstream
json;
418 uint64_t first_ts = 0, last_ts = 0;
420 first_ts =
events_[0].timestamp_ms;
424 json <<
"\"start_ms\":" << first_ts <<
",";
425 json <<
"\"end_ms\":" << last_ts <<
",";
426 json <<
"\"duration_ms\":" << (last_ts - first_ts) <<
",";
427 json <<
"\"event_count\":" <<
events_.size() <<
",";
430 json <<
"\"by_location\":{";
431 std::map<std::string, int> location_counts;
432 for (
const auto& e :
events_) {
433 location_counts[e.location]++;
436 for (
const auto& [loc, count] : location_counts) {
439 json <<
"\"" << loc <<
"\":" << count;
445 int info_count = 0, warn_count = 0, error_count = 0;
446 for (
const auto& e :
events_) {
454 json <<
"\"info_count\":" << info_count <<
",";
455 json <<
"\"warning_count\":" << warn_count <<
",";
456 json <<
"\"error_count\":" << error_count;
462std::string PaletteDebugger::GetDiagnosticSummary()
const {
463 std::ostringstream summary;
466 int warnings = 0, errors = 0;
467 bool has_palette_timing_issue =
false;
468 bool has_missing_palette =
false;
469 bool has_color_mismatch =
false;
471 for (
const auto& e :
events_) {
478 if (e.message.find(
"WITHOUT palette") != std::string::npos) {
479 has_missing_palette =
true;
481 if (e.message.find(
"only") != std::string::npos &&
482 e.message.find(
"colors") != std::string::npos) {
483 has_palette_timing_issue =
true;
490 has_color_mismatch =
true;
495 summary <<
"Events: " <<
events_.size() <<
", Warnings: " << warnings
496 <<
", Errors: " << errors <<
". ";
498 if (has_missing_palette) {
499 summary <<
"ISSUE: Texture created without palette. ";
501 if (has_palette_timing_issue) {
502 summary <<
"ISSUE: Palette may not be fully loaded. ";
504 if (has_color_mismatch) {
505 summary <<
"ISSUE: Color mismatch detected between expected and actual. ";
507 if (!has_missing_palette && !has_palette_timing_issue &&
508 !has_color_mismatch) {
509 summary <<
"No obvious issues detected. ";
512 return summary.str();
515std::string PaletteDebugger::GetHypothesisAnalysis()
const {
516 std::ostringstream analysis;
519 bool texture_before_palette =
false;
520 int last_palette_load_seq = -1;
521 int first_texture_create_seq = INT_MAX;
523 for (
const auto& e :
events_) {
524 if (e.message.find(
"Loaded palette") != std::string::npos) {
525 last_palette_load_seq =
526 std::max(last_palette_load_seq, e.sequence_number);
528 if (e.message.find(
"Creating texture") != std::string::npos) {
529 first_texture_create_seq =
530 std::min(first_texture_create_seq, e.sequence_number);
534 if (first_texture_create_seq < last_palette_load_seq) {
535 texture_before_palette =
true;
536 analysis <<
"TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
537 << first_texture_create_seq <<
") before palette loaded (seq "
538 << last_palette_load_seq <<
"). ";
539 analysis <<
"FIX: Ensure palette is applied to surface BEFORE queuing "
540 "texture creation. ";
541 }
else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
542 analysis <<
"Timing appears correct: palette loaded (seq "
543 << last_palette_load_seq <<
") before texture created (seq "
544 << first_texture_create_seq <<
"). ";
548 bool wrong_palette_group =
false;
549 for (
const auto& e :
events_) {
550 if (e.color_count > 0 && e.color_count < 90) {
551 wrong_palette_group =
true;
552 analysis <<
"PALETTE GROUP ISSUE: Only " << e.color_count
553 <<
" colors (expected 90). Check palette group selection. ";
559 for (
const auto& e :
events_) {
560 if (e.message.find(
"NOT indexed") != std::string::npos) {
561 analysis <<
"FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
566 if (analysis.str().empty()) {
567 analysis <<
"No hypothesis conditions detected. Render pipeline may be "
568 "correct. Check actual colors vs expected in comparisons. ";
571 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 LogSurfaceState(const std::string &location, SDL_Surface *surface)
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