Skip to content

Instantly share code, notes, and snippets.

@Pokechu22
Last active April 18, 2021 22:35
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/55cb365518defd58d6a294c22e566787 to your computer and use it in GitHub Desktop.
Save Pokechu22/55cb365518defd58d6a294c22e566787 to your computer and use it in GitHub Desktop.

Adapted from my indirect coords test case.

NOTE: Cases where the file ends in 0Y exhibit undefined behavior by using an indirect texture with the number of indirect stages set to 0. Their results are not important; they are simply included to simplify writing the test case.

#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"};
const char* wraps[8] = {
"ITW_OFF", "ITW_256", "ITW_128", "ITW_64",
"ITW_32", "ITW_16", "ITW_0", "Invalid"
};
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, int wrap);
void save_screenshot(char* dir, int frame_counter, int ind_stages, bool use_matrix, int wrap);
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*8; 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;
int wrap = (number % 8); number /= 8; // GX_ITW_OFF - GX_ITW_0 (0-6) plus invalid (7) for the sake of completeness
// Newline first to avoid having the only visible line be blank
printf("\nFrame: %02d; Ind stages: %d; Matrix: %c; Wrap: %s", frame_counter,
ind_stages, use_matrix ? 'Y' : 'N', wraps[wrap]);
run_test(ind_stages, use_matrix, wrap);
GX_DrawDone();
save_screenshot(screenshotfolder, frame_counter, ind_stages, use_matrix, wrap);
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, int wrap) {
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(1);
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);
// Scaled diagonal matrix
f32 offset_mtx[2][3] = {{0.25, 0, 0}, {0, 0.25, 0}};
GX_SetIndTexMatrix(GX_ITM_0, offset_mtx, 0);
GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL);
GX_SetTevOp(GX_TEVSTAGE0, GX_DECAL);
GX_SetTevIndirect(GX_TEVSTAGE0, GX_INDTEXSTAGE0, GX_ITF_8, GX_ITB_STU, use_matrix ? GX_ITM_0 : GX_ITM_OFF, wrap, wrap, GX_FALSE, 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, int wrap) {
// 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_%s_%d%c.png",
dir, frame_counter, wrap, wraps[wrap], ind_stages, use_matrix ? '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;
}
}
#---------------------------------------------------------------------------------
# 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
View raw

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

View raw

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

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