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 OK: direct-color format=0x%08X; no palette required", fmt);
163 }
else if (!has_palette) {
165 event.message =
"Surface is indexed but has NO palette attached!";
168 event.message = absl::StrFormat(
169 "Surface OK: indexed format, %d-color palette attached", ncolors);
172 if (palette && ncolors > 56) {
173 auto& c = palette->colors[56];
174 event.sample_colors = {c.r, c.g, c.b};
176 absl::StrFormat(
" [Color56: R=%d G=%d B=%d]", c.r, c.g, c.b);
181 LogPaletteDebugEvent(event);
189 const std::vector<SDL_Color>& palette) {
199 const std::vector<SDL_Color>& render_palette) {
228 if (x < 0 || x >= width || y < 0 || y >= height) {
244 if (!data || data_size == 0) {
248 size_t idx =
static_cast<size_t>(y * width + x);
249 if (idx >= data_size) {
265 comp.
expected_r =
static_cast<uint8_t
>(rgb.x);
266 comp.
expected_g =
static_cast<uint8_t
>(rgb.y);
267 comp.
expected_b =
static_cast<uint8_t
>(rgb.z);
290 for (
size_t i = 0; i < palette.
size(); i++) {
291 auto rgb = palette[i].rgb();
292 sum +=
static_cast<uint32_t
>(rgb.x) * (i + 1);
293 sum +=
static_cast<uint32_t
>(rgb.y) * (i + 1) * 256;
294 sum +=
static_cast<uint32_t
>(rgb.z) * (i + 1) * 65536;
300std::string PaletteDebugger::ExportToJSON()
const {
301 std::ostringstream
json;
303 for (
size_t i = 0; i <
events_.size(); i++) {
306 json <<
"\"location\":\"" << e.location <<
"\",";
307 json <<
"\"message\":\"" << e.message <<
"\",";
308 json <<
"\"level\":\""
313 json <<
"\"palette_id\":" << e.palette_id <<
",";
314 json <<
"\"color_count\":" << e.color_count;
317 if (!e.sample_colors.empty() && e.sample_colors.size() >= 3) {
318 json <<
",\"sample_rgb\":[" << (int)e.sample_colors[0] <<
","
319 << (
int)e.sample_colors[1] <<
"," << (int)e.sample_colors[2] <<
"]";
330std::string PaletteDebugger::ExportColorComparisonsJSON()
const {
331 std::ostringstream
json;
336 json <<
"\"x\":" << c.x <<
",\"y\":" << c.y <<
",";
337 json <<
"\"palette_index\":" << (int)c.palette_index <<
",";
338 json <<
"\"actual\":[" << (int)c.actual_r <<
"," << (
int)c.actual_g <<
","
339 << (int)c.actual_b <<
"],";
340 json <<
"\"expected\":[" << (int)c.expected_r <<
"," << (
int)c.expected_g
341 <<
"," << (int)c.expected_b <<
"],";
342 json <<
"\"matches\":" << (c.matches ?
"true" :
"false");
351std::string PaletteDebugger::SamplePixelJSON(
int x,
int y)
const {
353 std::ostringstream
json;
355 json <<
"\"x\":" << comp.x <<
",\"y\":" << comp.y <<
",";
356 json <<
"\"palette_index\":" << (int)comp.palette_index <<
",";
357 json <<
"\"actual\":[" << (int)comp.actual_r <<
"," << (
int)comp.actual_g
358 <<
"," << (int)comp.actual_b <<
"],";
359 json <<
"\"expected\":[" << (int)comp.expected_r <<
","
360 << (
int)comp.expected_g <<
"," << (int)comp.expected_b <<
"],";
361 json <<
"\"matches\":" << (comp.matches ?
"true" :
"false");
366std::string PaletteDebugger::ExportFullStateJSON()
const {
367 std::ostringstream
json;
371 json <<
"\"events\":" << ExportToJSON() <<
",";
374 json <<
"\"comparisons\":" << ExportColorComparisonsJSON() <<
",";
377 json <<
"\"palette\":" << ExportPaletteDataJSON() <<
",";
380 json <<
"\"timeline\":" << ExportTimelineJSON() <<
",";
383 json <<
"\"diagnostic\":\"" << GetDiagnosticSummary() <<
"\",";
386 json <<
"\"hypothesis\":\"" << GetHypothesisAnalysis() <<
"\"";
392std::string PaletteDebugger::ExportPaletteDataJSON()
const {
393 std::ostringstream
json;
398 json <<
"\"colors\":[";
402 json <<
"{\"index\":" << i <<
",\"r\":" << (int)rgb.x
403 <<
",\"g\":" << (
int)rgb.y <<
",\"b\":" << (int)rgb.z <<
"}";
409 json <<
"\"render_colors\":[";
411 bool first_render =
true;
420 json <<
"{\"index\":" << i <<
",\"r\":" <<
static_cast<int>(color.r)
421 <<
",\"g\":" <<
static_cast<int>(color.g)
422 <<
",\"b\":" <<
static_cast<int>(color.b)
423 <<
",\"a\":" <<
static_cast<int>(color.a) <<
"}";
424 first_render =
false;
431std::string PaletteDebugger::ExportTimelineJSON()
const {
432 std::ostringstream
json;
436 uint64_t first_ts = 0, last_ts = 0;
438 first_ts =
events_[0].timestamp_ms;
442 json <<
"\"start_ms\":" << first_ts <<
",";
443 json <<
"\"end_ms\":" << last_ts <<
",";
444 json <<
"\"duration_ms\":" << (last_ts - first_ts) <<
",";
445 json <<
"\"event_count\":" <<
events_.size() <<
",";
448 json <<
"\"by_location\":{";
449 std::map<std::string, int> location_counts;
450 for (
const auto& e :
events_) {
451 location_counts[e.location]++;
454 for (
const auto& [loc, count] : location_counts) {
457 json <<
"\"" << loc <<
"\":" << count;
463 int info_count = 0, warn_count = 0, error_count = 0;
464 for (
const auto& e :
events_) {
472 json <<
"\"info_count\":" << info_count <<
",";
473 json <<
"\"warning_count\":" << warn_count <<
",";
474 json <<
"\"error_count\":" << error_count;
480std::string PaletteDebugger::GetDiagnosticSummary()
const {
481 std::ostringstream summary;
484 int warnings = 0, errors = 0;
485 bool has_palette_timing_issue =
false;
486 bool has_missing_palette =
false;
487 bool has_color_mismatch =
false;
489 for (
const auto& e :
events_) {
496 if (e.message.find(
"WITHOUT palette") != std::string::npos) {
497 has_missing_palette =
true;
499 if (e.message.find(
"only") != std::string::npos &&
500 e.message.find(
"colors") != std::string::npos) {
501 has_palette_timing_issue =
true;
507 if (c.palette_index >= 0 && !c.matches) {
508 has_color_mismatch =
true;
513 summary <<
"Events: " <<
events_.size() <<
", Warnings: " << warnings
514 <<
", Errors: " << errors <<
". ";
516 if (has_missing_palette) {
517 summary <<
"ISSUE: Texture created without palette. ";
519 if (has_palette_timing_issue) {
520 summary <<
"ISSUE: Palette may not be fully loaded. ";
522 if (has_color_mismatch) {
523 summary <<
"ISSUE: Color mismatch detected between expected and actual. ";
525 if (!has_missing_palette && !has_palette_timing_issue &&
526 !has_color_mismatch) {
527 summary <<
"No obvious issues detected. ";
530 return summary.str();
533std::string PaletteDebugger::GetHypothesisAnalysis()
const {
534 std::ostringstream analysis;
537 bool texture_before_palette =
false;
538 int last_palette_load_seq = -1;
539 int first_texture_create_seq = INT_MAX;
541 for (
const auto& e :
events_) {
542 if (e.message.find(
"Loaded palette") != std::string::npos) {
543 last_palette_load_seq =
544 std::max(last_palette_load_seq, e.sequence_number);
546 if (e.message.find(
"Creating texture") != std::string::npos) {
547 first_texture_create_seq =
548 std::min(first_texture_create_seq, e.sequence_number);
552 if (first_texture_create_seq < last_palette_load_seq) {
553 texture_before_palette =
true;
554 analysis <<
"TIMING HYPOTHESIS CONFIRMED: Texture created (seq "
555 << first_texture_create_seq <<
") before palette loaded (seq "
556 << last_palette_load_seq <<
"). ";
557 analysis <<
"FIX: Ensure palette is applied to surface BEFORE queuing "
558 "texture creation. ";
559 }
else if (last_palette_load_seq >= 0 && first_texture_create_seq < INT_MAX) {
560 analysis <<
"Timing appears correct: palette loaded (seq "
561 << last_palette_load_seq <<
") before texture created (seq "
562 << first_texture_create_seq <<
"). ";
567 bool wrong_palette_group =
false;
570 for (
const auto& e :
events_) {
571 if (e.message.find(
"NOT indexed") != std::string::npos) {
572 analysis <<
"FORMAT MISMATCH: Surface is not indexed, palette ignored. ";
577 if (analysis.str().empty()) {
578 analysis <<
"No hypothesis conditions detected. Render pipeline may be "
579 "correct. Check actual colors vs expected in comparisons. ";
582 return analysis.str();
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
SDL_Color ReadColor(int x, int y) 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