Skip to content

Instantly share code, notes, and snippets.

@Pokechu22
Last active April 18, 2021 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pokechu22/8396169343eab4f759cbaa279fb66d78 to your computer and use it in GitHub Desktop.
Save Pokechu22/8396169343eab4f759cbaa279fb66d78 to your computer and use it in GitHub Desktop.
#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 4
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 ind_stages, bool use_matrix, bool add_prev);
void save_screenshot(char* dir, int frame_counter, int ind_stages, bool use_matrix, bool add_prev);
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 < 2*2*2; 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 ind_stages = (number % 2); number /= 2;
bool use_matrix = (number % 2); number /= 2;
bool add_prev = (number % 2); number /= 2;
// Newline first to avoid having the only visible line be blank
printf("\nFrame: %02d; Ind stages: %d; Matrix: %c; Add prev: %c", frame_counter,
ind_stages, use_matrix ? 'Y' : 'N', add_prev ? 'Y' : 'N');
run_test(ind_stages, use_matrix, add_prev);
GX_DrawDone();
save_screenshot(screenshotfolder, frame_counter, ind_stages, use_matrix, add_prev);
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*.6, t*.4);
}
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 ind_stages, bool use_matrix, bool add_prev) {
Mtx v; // view matrix
GXTexObj* base_tex = base_texs[1];
GXTexObj* ind_tex = ind_texs[0];
// Actually configure and render
GX_SetNumChans(0);
GX_SetNumTexGens(1);
GX_SetNumIndStages(ind_stages);
GX_SetNumTevStages(2);
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_INDEX8);
GX_SetVtxDesc(GX_VA_TEX0, 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_SetArray(GX_VA_POS, square, 3*sizeof(f32));
GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
GX_SetIndTexOrder(GX_INDTEXSTAGE0, GX_TEXCOORD0, GX_TEXMAP1);
// 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, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL);
GX_SetTevOp(GX_TEVSTAGE0, GX_DECAL);
GX_SetTevDirect(GX_TEVSTAGE0);
//GX_SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLORNULL);
GX_SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL);
GX_SetTevOp(GX_TEVSTAGE1, GX_DECAL);
GX_SetTevIndirect(GX_TEVSTAGE1, GX_INDTEXSTAGE0, GX_ITF_8, GX_ITB_STU, use_matrix ? GX_ITM_0 : GX_ITM_OFF, GX_ITW_OFF, GX_ITW_OFF, add_prev, GX_FALSE, GX_ITBA_OFF);
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 ind_stages, bool use_matrix, bool add_prev) {
// 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/F%02d_%d%c%c.png",
dir, frame_counter, ind_stages, use_matrix ? 'Y' : 'N', add_prev ? 'Y' : 'N');
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;
}
}
# Original implementation (bash), didn't give very nice results
# function compareImages {
# # $1 is input dir 1, $2 is input dir 2, $3 is output dir
# for file in $(cd $1; ls *.png); do
# # Uses imagemagick
# compare $1/$file $2/$file $3/$file
# done
# }
# I also tried https://stackoverflow.com/a/8505681 but it was WAY too slow
# From https://stackoverflow.com/a/45362102
import cv2
import os
import sys
assert len(sys.argv) == 4
dira = sys.argv[1]
dirb = sys.argv[2]
outdir = sys.argv[3]
for f in os.listdir(dira):
if f.lower().endswith("png"):
a = cv2.imread(os.path.join(dira, f))
b = cv2.imread(os.path.join(dirb, f))
cv2.imwrite(os.path.join(outdir, f), cv2.bitwise_xor(a, b))
#---------------------------------------------------------------------------------
# Clear the implicit built in rules
#---------------------------------------------------------------------------------
.SUFFIXES:
.SECONDARY:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPPC)),)
$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=<path to>devkitPPC")
endif
include $(DEVKITPPC)/wii_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := .
DATA :=
TEXTURES := .
INCLUDES :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE)
CXXFLAGS = $(CFLAGS)
LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -logc -lm -lfat -lpng -lz
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(TEXTURES),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
#---------------------------------------------------------------------------------
# automatically build a list of object files for our project
#---------------------------------------------------------------------------------
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
SCFFILES := $(foreach dir,$(TEXTURES),$(notdir $(wildcard $(dir)/*.scf)))
TPLFILES := $(SCFFILES:.scf=.tpl)
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
export OFILES_BIN := $(addsuffix .o,$(BINFILES)) $(addsuffix .o,$(TPLFILES))
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(sFILES:.s=.o) $(SFILES:.S=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SOURCES)
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(addsuffix .h,$(subst .,_,$(TPLFILES)))
#---------------------------------------------------------------------------------
# build a list of include paths
#---------------------------------------------------------------------------------
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD) \
-I$(LIBOGC_INC)
#---------------------------------------------------------------------------------
# build a list of library paths
#---------------------------------------------------------------------------------
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \
-L$(LIBOGC_LIB)
export OUTPUT := $(CURDIR)/$(TARGET)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).dol: $(OUTPUT).elf
$(OUTPUT).elf: $(OFILES)
$(OFILES_SOURCES) : $(HFILES)
#---------------------------------------------------------------------------------
# This rule links in binary data with the .bin extension
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.tpl.o %_tpl.h : %.tpl
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
#---------------------------------------------------------------------------------
run:
wiiload $(TARGET).dol
This file has been truncated, but you can view the full file.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment