|
#include <stdio.h> |
|
#include <stdbool.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <malloc.h> |
|
#include <math.h> |
|
#include <gccore.h> |
|
#include <fat.h> |
|
#include <time.h> |
|
#include <sys/stat.h> |
|
#include <sdcard/wiisd_io.h> |
|
#include <png.h> |
|
#include <zlib.h> |
|
|
|
#include <ogc/consol.h> |
|
#include <ogc/tpl.h> |
|
#include <ogc/stm.h> |
|
|
|
#include "textures_tpl.h" |
|
#include "textures.h" |
|
|
|
#define DEFAULT_FIFO_SIZE (256*1024) |
|
|
|
#define NUM_FRAMES 30 |
|
|
|
typedef struct tagcamera { |
|
guVector pos; |
|
guVector up; |
|
guVector view; |
|
}camera; |
|
f32 square[] ATTRIBUTE_ALIGN(32) = |
|
{ |
|
// x y z |
|
-50, 50, 0, // 0 |
|
50, 50, 0, // 1 |
|
50, -50, 0, // 2 |
|
-50, -50, 0, // 3 |
|
}; |
|
|
|
static void *xfb = NULL; |
|
static u8 *console_buffer = NULL; |
|
static u32 *efb_buffer = NULL; // For screenshots |
|
static uLong frame_crcs[NUM_FRAMES]; |
|
static u32 do_copy = GX_FALSE; |
|
GXRModeObj *rmode; |
|
GXTexObj image_base_160_tex, image_base_256_tex; |
|
GXTexObj image_ind_tex, image_ind_greyer_tex, image_ind_trans_tex; |
|
|
|
#define NUM_BASE 2 |
|
GXTexObj* base_texs[NUM_BASE] = {&image_base_160_tex, &image_base_256_tex}; |
|
const char* base_names[NUM_BASE] = {"160x160", "256x256"}; |
|
#define NUM_IND 3 |
|
GXTexObj* ind_texs[NUM_IND] = {&image_ind_tex, &image_ind_greyer_tex, &image_ind_trans_tex}; |
|
const char* ind_names[NUM_IND] = {"Normal", "Greyer", "Transp"}; |
|
|
|
const char* matrix_names[2] = {"Luigi", "Ident"}; |
|
|
|
TPLFile spriteTPL; |
|
|
|
camera cam = {{0.0F, 0.0F, 0.0F}, |
|
{0.0F, 1.0F, 0.0F}, |
|
{0.0F, 0.0F, -1.0F}}; |
|
|
|
static void draw_vert(u8 pos, f32 s, f32 t); |
|
static void draw_square(Mtx v); |
|
void run_test(int num_tex_gens, int base_image, int ind_image, int base_coord, int ind_coord, bool use_identity_matrix); |
|
void save_screenshot(char* dir, int frame_counter, int num_tex_gens, int base_image, int ind_image, int base_coord, int ind_coord, bool use_identity_matrix); |
|
static void save_rgba_png(char* fn, u32 *buffer, u16 width, u16 height); |
|
static void copy_to_xfb(u32 count); |
|
|
|
int main() { |
|
Mtx p; // perspective matrix |
|
GXColor background = {0, 0, 0, 0xff}; |
|
|
|
VIDEO_Init(); |
|
|
|
rmode = VIDEO_GetPreferredMode(NULL); |
|
|
|
PAD_Init(); |
|
xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); |
|
|
|
// Characters are 16 pixels tall, and the XFB uses 2 bytes per pixel |
|
console_buffer = malloc(2*rmode->fbWidth*16); |
|
CON_Init(console_buffer, 0, 0, rmode->fbWidth, 16, rmode->fbWidth*VI_DISPLAY_PIX_SZ); |
|
|
|
VIDEO_Configure(rmode); |
|
VIDEO_SetNextFramebuffer(xfb); |
|
VIDEO_SetPostRetraceCallback(copy_to_xfb); |
|
VIDEO_SetBlack(FALSE); |
|
VIDEO_Flush(); |
|
VIDEO_WaitVSync(); |
|
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync(); |
|
|
|
void *gp_fifo = NULL; |
|
gp_fifo = MEM_K0_TO_K1(memalign(32,DEFAULT_FIFO_SIZE)); |
|
memset(gp_fifo,0,DEFAULT_FIFO_SIZE); |
|
|
|
GX_Init(gp_fifo,DEFAULT_FIFO_SIZE); |
|
|
|
GX_SetCopyClear(background, 0x00ffffff); |
|
|
|
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1); |
|
GX_SetDispCopyYScale((f32)rmode->xfbHeight/(f32)rmode->efbHeight); |
|
GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight); |
|
GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight); |
|
GX_SetDispCopyDst(rmode->fbWidth,rmode->xfbHeight); |
|
GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter); |
|
GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE)); |
|
|
|
if (rmode->aa) { |
|
GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR); |
|
} else { |
|
GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR); |
|
} |
|
|
|
GX_SetCullMode(GX_CULL_NONE); |
|
GX_CopyDisp(xfb,GX_TRUE); |
|
GX_SetDispCopyGamma(GX_GM_1_0); |
|
|
|
guPerspective(p, 60, 1.33F, 10.0F, 1000.0F); |
|
GX_LoadProjectionMtx(p, GX_PERSPECTIVE); |
|
|
|
GX_InvalidateTexAll(); |
|
|
|
TPL_OpenTPLFromMemory(&spriteTPL, (void *)textures_tpl,textures_tpl_size); |
|
TPL_GetTexture(&spriteTPL,image_base_160,&image_base_160_tex); |
|
TPL_GetTexture(&spriteTPL,image_base_256,&image_base_256_tex); |
|
TPL_GetTexture(&spriteTPL,image_ind,&image_ind_tex); |
|
TPL_GetTexture(&spriteTPL,image_ind_greyer,&image_ind_greyer_tex); |
|
TPL_GetTexture(&spriteTPL,image_ind_trans,&image_ind_trans_tex); |
|
|
|
fatMountSimple("sd", &__io_wiisd); |
|
|
|
time_t rawtime; |
|
time(&rawtime); |
|
struct tm* curtime = localtime(&rawtime); |
|
char screenshotfolder[100]; |
|
strftime(screenshotfolder, sizeof(screenshotfolder), "sd:/Test_%H%M%S", curtime); |
|
|
|
mkdir(screenshotfolder, 0777); |
|
|
|
efb_buffer = malloc(rmode->fbWidth*rmode->efbHeight*sizeof(u32)); |
|
|
|
printf("\x1b[30;0m\x1b[47;1m"); // Switch to black text on a white background. |
|
|
|
for (u32 iteration = 0; iteration < 4*3*4; iteration++) { |
|
memset(frame_crcs, 0, sizeof(frame_crcs)); |
|
|
|
for (u32 frame_counter = 0; frame_counter < NUM_FRAMES; frame_counter++) { |
|
// Compute configuration -- done each frame so that the loops can be swapped to make recording a FIFO easier |
|
u32 number = iteration; |
|
int num_tex_gens = (number % 4); number /= 4; |
|
int base_image = 1; // (number % NUM_BASE); number /= NUM_BASE; |
|
int ind_image = 0; // (number % NUM_IND ); number /= NUM_IND; |
|
int base_coord = (number % 3); number /= 3; // GX_TEXCOORD0-GX_TEXCOORD2 are just 0-2 |
|
int ind_coord = (number % 4) - 1; number /= 4; // -1 => disabled |
|
bool use_identity_matrix = true; // !(number % 2); number /= 2; |
|
|
|
// Newline first to avoid having the only visible line be blank |
|
char ind_coord_display = ind_coord == -1 ? 'X' : ind_coord + '0'; |
|
printf("\nFrame: %02d; Tex gens: %d; Base: %s; Ind: %s; BC: %d; IC: %c; Matrix: %s", frame_counter, num_tex_gens, |
|
base_names[base_image], ind_names[ind_image], base_coord, ind_coord_display, matrix_names[use_identity_matrix]); |
|
|
|
run_test(num_tex_gens, base_image, ind_image, base_coord, ind_coord, use_identity_matrix); |
|
|
|
GX_DrawDone(); |
|
save_screenshot(screenshotfolder, frame_counter, num_tex_gens, base_image, ind_image, base_coord, ind_coord, use_identity_matrix); |
|
|
|
do_copy = GX_TRUE; |
|
VIDEO_WaitVSync(); |
|
|
|
PAD_ScanPads(); |
|
|
|
if(PAD_ButtonsDown(0) & PAD_BUTTON_START) { |
|
exit(0); |
|
} |
|
} |
|
} |
|
STM_ShutdownToStandby(); |
|
return 0; |
|
} |
|
|
|
void draw_vert(u8 pos, f32 s, f32 t) { |
|
GX_Position1x8(pos); |
|
GX_TexCoord2f32(s*2, t*2); |
|
GX_TexCoord2f32(s, t); |
|
GX_TexCoord2f32(s*3, t*3); |
|
} |
|
|
|
void draw_square(Mtx v) { |
|
Mtx m; // model matrix. |
|
Mtx mv; // modelview matrix. |
|
|
|
guMtxIdentity(m); |
|
guMtxTransApply(m, m, 0, 0, -100); |
|
|
|
guMtxConcat(v,m,mv); |
|
GX_LoadPosMtxImm(mv, GX_PNMTX0); |
|
|
|
GX_Begin(GX_QUADS, GX_VTXFMT0, 4); |
|
draw_vert(0, 0.0, 0.0); |
|
draw_vert(1, 1.0, 0.0); |
|
draw_vert(2, 1.0, 1.0); |
|
draw_vert(3, 0.0, 1.0); |
|
GX_End(); |
|
} |
|
|
|
void run_test(int num_tex_gens, int base_image, int ind_image, int base_coord, int ind_coord, bool use_identity_matrix) { |
|
Mtx v; // view matrix |
|
|
|
GXTexObj* base_tex = base_texs[base_image]; |
|
GXTexObj* ind_tex = ind_texs[ind_image]; |
|
|
|
// Actually configure and render |
|
GX_SetNumChans(0); |
|
GX_SetNumTexGens(num_tex_gens); |
|
GX_SetNumIndStages(1); |
|
GX_SetNumTevStages(1); |
|
|
|
GX_ClearVtxDesc(); |
|
GX_SetVtxDesc(GX_VA_POS, GX_INDEX8); |
|
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); |
|
GX_SetVtxDesc(GX_VA_TEX1, GX_DIRECT); |
|
GX_SetVtxDesc(GX_VA_TEX2, GX_DIRECT); |
|
|
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); |
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); |
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX1, GX_TEX_ST, GX_F32, 0); |
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX2, GX_TEX_ST, GX_F32, 0); |
|
|
|
GX_SetArray(GX_VA_POS, square, 3*sizeof(f32)); |
|
|
|
GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); |
|
GX_SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY); |
|
GX_SetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX2, GX_IDENTITY); |
|
|
|
if (ind_coord != -1) { |
|
GX_SetIndTexOrder(GX_INDTEXSTAGE0, ind_coord, GX_TEXMAP1); |
|
if (use_identity_matrix) { |
|
// Identity matrix (requires dividing values by 2 and using a scale 1 higher |
|
// due to internal representation, as noted in the docs for GX_SetIndTexMatrix) |
|
f32 offset_mtx[2][3] = {{.5f, 0, 0}, {0, .5f, 0}}; |
|
GX_SetIndTexMatrix(GX_ITM_0, offset_mtx, 1); |
|
} else { |
|
// This is what Luigi's Mansion does |
|
f32 offset_mtx[2][3] = {{0.009765625f, 0.009765625f, 0}, {0.009765625f, 0.009765625f, 0}}; |
|
GX_SetIndTexMatrix(GX_ITM_0, offset_mtx, 1); |
|
} |
|
} |
|
|
|
GX_SetTevOrder(GX_TEVSTAGE0, base_coord, GX_TEXMAP0, GX_COLORNULL); |
|
GX_SetTevOp(GX_TEVSTAGE0, GX_DECAL); |
|
if (ind_coord != -1) { |
|
GX_SetTevIndirect(GX_TEVSTAGE0, GX_INDTEXSTAGE0, GX_ITF_8, GX_ITB_STU, GX_ITM_0, GX_ITW_OFF, GX_ITW_OFF, GX_FALSE, GX_FALSE, GX_ITBA_OFF); |
|
} else { |
|
GX_SetTevDirect(GX_TEVSTAGE0); |
|
} |
|
|
|
GX_LoadTexObj(base_tex, GX_TEXMAP0); |
|
GX_LoadTexObj(ind_tex, GX_TEXMAP1); |
|
|
|
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1); |
|
guLookAt(v, &cam.pos, &cam.up, &cam.view); |
|
GX_InvVtxCache(); |
|
GX_Flush(); |
|
|
|
draw_square(v); |
|
} |
|
|
|
void save_screenshot(char* dir, int frame_counter,int num_tex_gens, int base_image, int ind_image, int base_coord, int ind_coord, bool use_identity_matrix) { |
|
// Based on Open Homebrew Channel code (GPLv2): |
|
// https://github.com/fail0verflow/hbc/blob/a8e5f6c0f7e484c7f7112967eee6eee47b27d9ac/channel/channelapp/source/gfx.c#L506-L538 |
|
// https://github.com/fail0verflow/hbc/blob/a8e5f6c0f7e484c7f7112967eee6eee47b27d9ac/channel/channelapp/source/view.c#L252-L269 |
|
u16 x, y; |
|
GXColor c; |
|
u32 val; |
|
u32 *p = efb_buffer; |
|
|
|
for (y = 0; y < rmode->efbHeight; ++y) { |
|
for (x = 0; x < rmode->fbWidth; ++x) { |
|
GX_PeekARGB(x, y, &c); |
|
val = ((u32) c.a) << 24; |
|
val |= ((u32) c.r) << 16; |
|
val |= ((u32) c.g) << 8; |
|
val |= c.b; |
|
|
|
*p++ = val; |
|
} |
|
} |
|
|
|
uLong crc = crc32(0L, Z_NULL, 0); |
|
crc = crc32(crc, (u8*)efb_buffer, rmode->fbWidth*rmode->efbHeight*sizeof(u32)); |
|
|
|
frame_crcs[frame_counter] = crc; |
|
|
|
// Check if the screenshot matches. |
|
for (u32 i = 0; i < frame_counter; i++) { |
|
if (frame_crcs[i] == crc) { |
|
// This result has already been seen |
|
return; |
|
} |
|
} |
|
|
|
char filename[256]; |
|
// snprintf(filename, sizeof(filename), "%s/%s_%s_%s_BC%d_IC%d_%dTG_F%02d.png", |
|
// dir, matrix_names[use_identity_matrix], |
|
// base_names[base_image], ind_names[ind_image], |
|
// base_coord, ind_coord, num_tex_gens, frame_counter); |
|
char ind_coord_display = ind_coord == -1 ? 'X' : ind_coord + '0'; |
|
snprintf(filename, sizeof(filename), "%s/F%02d_BC%d_IC%c_TG%d.png", |
|
dir, frame_counter, base_coord, ind_coord_display, num_tex_gens); |
|
|
|
save_rgba_png(filename, efb_buffer, rmode->fbWidth, rmode->efbHeight); |
|
} |
|
|
|
static void save_rgba_png(char* fn, u32 *buffer, u16 width, u16 height) { |
|
// Code from Open Homebrew Channel (with x -> width and y -> height): |
|
// https://github.com/fail0verflow/hbc/blob/a8e5f6c0f7e484c7f7112967eee6eee47b27d9ac/channel/channelapp/source/tex.c#L182-L261 |
|
FILE *fp = NULL; |
|
png_structp png_ptr = NULL; |
|
png_infop info_ptr; |
|
png_bytep *row_pointers; |
|
u16 i; |
|
|
|
row_pointers = (png_bytep *) malloc(height * sizeof(png_bytep)); |
|
|
|
fp = fopen(fn, "wb"); |
|
if (!fp) { |
|
// gprintf("couldnt open %s for writing\n", fn); |
|
goto exit; |
|
} |
|
|
|
setbuf(fp, NULL); |
|
|
|
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); |
|
if (!png_ptr) { |
|
// gprintf ("png_create_write_struct failed\n"); |
|
goto exit; |
|
} |
|
|
|
info_ptr = png_create_info_struct(png_ptr); |
|
if (!info_ptr) { |
|
// gprintf ("png_create_info_struct failed\n"); |
|
goto exit; |
|
} |
|
|
|
if (setjmp(png_jmpbuf(png_ptr))) { |
|
// gprintf ("setjmp failed\n"); |
|
goto exit; |
|
} |
|
|
|
png_init_io(png_ptr, fp); |
|
|
|
png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); |
|
png_set_IHDR(png_ptr, info_ptr, width, height, 8, |
|
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, |
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); |
|
|
|
png_write_info(png_ptr, info_ptr); |
|
|
|
for (i = 0; i < height; ++i) |
|
row_pointers[i] = (png_bytep) (buffer + i * width); |
|
|
|
png_set_swap_alpha(png_ptr); |
|
|
|
png_write_image(png_ptr, row_pointers); |
|
png_write_end(png_ptr, info_ptr); |
|
|
|
// gprintf("saved %s\n", fn); |
|
|
|
exit: |
|
if (png_ptr) |
|
png_destroy_write_struct(&png_ptr, (png_infopp)NULL); |
|
|
|
free(row_pointers); |
|
fclose(fp); |
|
} |
|
|
|
// copy efb to xfb when ready |
|
static void copy_to_xfb(u32 count) { |
|
if(do_copy==GX_TRUE) { |
|
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); |
|
GX_SetColorUpdate(GX_TRUE); |
|
GX_CopyDisp(xfb,GX_TRUE); |
|
GX_Flush(); |
|
// Display the console at the top... by copying it into the xfb. |
|
// This requires "defer EFB copies to RAM" to be unchecked in Dolphin. |
|
// It does not work on the FIFO player at all. |
|
for (int i = 0; i < 6; i++) { |
|
// Loop is needed to make things show up properly on hardware |
|
// Otherwise it just ends up getting cut off in a weird way. |
|
// This might just be a timing thing and not something related to the framebuffer? |
|
memcpy(xfb+2*rmode->fbWidth*16, console_buffer, 2*rmode->fbWidth*16); |
|
} |
|
// This one doesn't need to be in a loop (though it is influenced by the number |
|
// of times the above loop runs) |
|
memcpy(xfb+2*rmode->fbWidth*448, console_buffer, 2*rmode->fbWidth*16); |
|
do_copy = GX_FALSE; |
|
} |
|
} |