benchmark-toolbox: add benchmark-toolbox

a unified benchmarking tool
This commit is contained in:
souldbminersmwc
2026-06-07 21:10:53 -04:00
parent 8ebf887956
commit 4a5e889c40
1252 changed files with 356811 additions and 491 deletions

View File

@@ -0,0 +1,390 @@
/*
* bench.c — CPU bandwidth + latency benchmarks and system info.
* Refactored from Membench-NX/main.c into result-returning functions with no
* console I/O, so a GUI (borealis) can drive them from a worker thread.
*
* Original bandwidth/latency methodology:
* Copyright (c) 2011 Siarhei Siamashka, (c) 20xx KazushiMe, (c) 2025 Souldbminer
*/
#include <math.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include "bench.h"
#include "gpu_bw.h"
#include <sys/time.h>
#define SIZE (32 * 1024 * 1024)
#define MAXREPEATS 10
#define LATBENCH_COUNT 10000000
#define ALIGN_PADDING 0x100000
#define CACHE_LINE_SIZE 128
struct f_data {
void (*func)(int64_t *, int64_t *, int);
int64_t *arg1;
int64_t *arg2;
int arg3;
};
static pthread_cond_t p_ready, p_start;
static pthread_mutex_t p_lock;
static pthread_t *p_worker = NULL;
static struct f_data *worker_data = NULL;
static int p_worker_not_ready, p_workers_ready;
static void *thread_func(void *data) {
struct f_data *d = data;
pthread_mutex_lock(&p_lock);
p_worker_not_ready--;
if (!p_worker_not_ready)
pthread_cond_signal(&p_ready);
while (p_workers_ready != 1)
pthread_cond_wait(&p_start, &p_lock);
pthread_mutex_unlock(&p_lock);
(d->func)(d->arg1, d->arg2, d->arg3);
pthread_exit(NULL);
}
static void parallel_run(void) {
pthread_mutex_lock(&p_lock);
p_workers_ready = 1;
pthread_mutex_unlock(&p_lock);
pthread_cond_broadcast(&p_start);
}
static void parallel_init(int threads) {
pthread_attr_t attr;
pthread_cond_init(&p_ready, NULL);
pthread_cond_init(&p_start, NULL);
pthread_mutex_init(&p_lock, NULL);
p_worker_not_ready = threads;
p_workers_ready = 0;
pthread_attr_init(&attr);
if (!p_worker || !worker_data) {
p_worker = malloc(threads * sizeof(pthread_t));
worker_data = malloc(threads * sizeof(struct f_data));
}
for (int i = 0; i < threads; i++)
pthread_create(p_worker + i, &attr, thread_func, worker_data + i);
pthread_mutex_lock(&p_lock);
while (p_worker_not_ready != 0)
pthread_cond_wait(&p_ready, &p_lock);
pthread_mutex_unlock(&p_lock);
}
static void aligned_block_copy(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
volatile int64_t *dst = dst_;
int64_t t1, t2, t3, t4;
while ((size -= 64) >= 0) {
t1 = *src++;
t2 = *src++;
t3 = *src++;
t4 = *src++;
*dst++ = t1;
*dst++ = t2;
*dst++ = t3;
*dst++ = t4;
t1 = *src++;
t2 = *src++;
t3 = *src++;
t4 = *src++;
*dst++ = t1;
*dst++ = t2;
*dst++ = t3;
*dst++ = t4;
}
}
static void aligned_block_fetch(int64_t *__restrict dst, int64_t *__restrict src_, int size) {
volatile int64_t *src = src_;
(void)dst;
while ((size -= 64) >= 0) {
*src++;
*src++;
*src++;
*src++;
*src++;
*src++;
*src++;
*src++;
}
}
static void aligned_block_fill(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
volatile int64_t *dst = dst_;
int64_t data = *src;
while ((size -= 64) >= 0) {
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
}
}
static double gettime(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (double)((int64_t)tv.tv_sec * 1000000 + tv.tv_usec) / 1000000.;
}
static double bandwidth_bench_helper(int threads, int64_t *dstbuf, int64_t *srcbuf, int size, void (*f)(int64_t *, int64_t *, int)) {
int i, loopcount, innerloopcount, n;
double t, t1, t2, speed, maxspeed, s, s0, s1, s2;
s = s0 = s1 = s2 = 0.;
maxspeed = 0.;
for (n = 0; n < MAXREPEATS; n++) {
loopcount = 0;
innerloopcount = 1;
t = 0.;
do {
loopcount += innerloopcount;
for (i = 0; i < innerloopcount; i++) {
parallel_init(threads);
for (int pt = 0; pt < threads; pt++) {
(worker_data + pt)->func = f;
(worker_data + pt)->arg1 = dstbuf + size * pt / sizeof(int64_t);
(worker_data + pt)->arg2 = srcbuf + size * pt / sizeof(int64_t);
(worker_data + pt)->arg3 = size;
}
t1 = gettime();
parallel_run();
for (int pt = 0; pt < threads; pt++)
pthread_join(p_worker[pt], NULL);
t2 = gettime();
t += t2 - t1;
}
innerloopcount *= 2;
} while (t < 0.5);
speed = (double)size * threads * loopcount / t / 1000000.;
s0 += 1.;
s1 += speed;
s2 += speed * speed;
if (speed > maxspeed)
maxspeed = speed;
if (s0 > 2.) {
s = sqrt((s0 * s2 - s1 * s1) / (s0 * (s0 - 1)));
if (s < maxspeed / 1000.)
break;
}
}
return maxspeed;
}
static char *align_up(char *ptr, int align) {
return (char *)(((uintptr_t)ptr + align - 1) & ~(uintptr_t)(align - 1));
}
static void *alloc_nonaliased_buffers(void **buf1_, int size1, void **buf2_, int size2, void **buf3_, int size3) {
char **buf1 = (char **)buf1_, **buf2 = (char **)buf2_, **buf3 = (char **)buf3_;
int mask = (ALIGN_PADDING - 1) & ~(CACHE_LINE_SIZE - 1);
char *buf = malloc(size1 + size2 + size3 + 9 * ALIGN_PADDING);
char *ptr = buf;
memset(buf, 0xCC, size1 + size2 + size3 + 9 * ALIGN_PADDING);
ptr = align_up(ptr, ALIGN_PADDING);
if (buf1) {
*buf1 = ptr + (0xAAAAAAAA & mask);
ptr = align_up(*buf1 + size1, ALIGN_PADDING);
}
if (buf2) {
*buf2 = ptr + (0x55555555 & mask);
ptr = align_up(*buf2 + size2, ALIGN_PADDING);
}
if (buf3) {
*buf3 = ptr + (0xCCCCCCCC & mask);
}
return buf;
}
#pragma GCC diagnostic push
static void __attribute__((noinline)) random_read_test(char *buf, int count, int nbits) {
uint32_t seed = 0;
uintptr_t mask = (1 << nbits) - 1;
uint32_t v;
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
static volatile uint32_t dummy;
#define RMA() \
seed = seed * 1103515245 + 12345; \
v = (seed >> 16) & 0xFF; \
seed = seed * 1103515245 + 12345; \
v |= (seed >> 8) & 0xFF00; \
seed = seed * 1103515245 + 12345; \
v |= seed & 0x7FFF0000; \
seed |= buf[v & mask];
while (count >= 16) {
RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() RMA() count -= 16;
}
dummy = seed;
#undef RMA
}
#pragma GCC diagnostic pop
static double latency_measure(char *buf, int nbits, int count) {
double t_noaccess = 0, t_before, t_after, t, xs1 = 0, xs2 = 0, min_t = 0;
double xs;
int n;
for (n = 1; n <= MAXREPEATS; n++) {
t_before = gettime();
random_read_test(buf, count, 1);
t_after = gettime();
if (n == 1 || t_after - t_before < t_noaccess)
t_noaccess = t_after - t_before;
}
for (n = 1; n <= MAXREPEATS; n++) {
t_before = gettime();
random_read_test(buf, count, nbits);
t_after = gettime();
t = t_after - t_before - t_noaccess;
if (t < 0)
t = 0;
xs1 += t;
xs2 += t * t;
if (n == 1 || t < min_t)
min_t = t;
if (n > 2) {
xs = sqrt((xs2 * n - xs1 * xs1) / (n * (n - 1)));
if (xs < min_t / 1000.)
break;
}
}
return min_t * 1000000000.0 / count;
}
static void latency_bench(double *l2_out, double *ram_out) {
char *buf_alloc = malloc(0x2001000);
char *buf = (char *)(((uintptr_t)buf_alloc + 4095) & ~(uintptr_t)4095);
memset(buf, 0, 0x2000000);
*l2_out = latency_measure(buf, 20, LATBENCH_COUNT);
*ram_out = latency_measure(buf, 25, LATBENCH_COUNT);
free(buf_alloc);
}
void bench_get_sysinfo(sysinfo_t *out) {
memset(out, 0, sizeof(*out));
out->threads = 3;
out->is_4gb = (appletGetAppletType() == AppletType_Application);
if (R_SUCCEEDED(clkrstInitialize())) {
ClkrstSession s;
clkrstOpenSession(&s, PcvModuleId_CpuBus, 3);
clkrstGetClockRate(&s, &out->cpu_hz);
clkrstCloseSession(&s);
clkrstOpenSession(&s, PcvModuleId_GPU, 3);
clkrstGetClockRate(&s, &out->gpu_hz);
clkrstCloseSession(&s);
clkrstOpenSession(&s, PcvModuleId_EMC, 3);
clkrstGetClockRate(&s, &out->mem_hz);
clkrstCloseSession(&s);
clkrstExit();
}
}
void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user) {
const int threads = 3;
const int size = SIZE;
bool is_4gb = (appletGetAppletType() == AppletType_Application);
memset(out, 0, sizeof(*out));
#define STEP(label, frac) \
do { \
if (progress) \
progress((label), (frac), user); \
} while (0)
STEP("GPU bandwidth", 0.05f);
gpu_bw_run(is_4gb, &out->gpu_copy, &out->gpu_read, &out->gpu_write);
int64_t *srcbuf, *dstbuf;
void *poolbuf = alloc_nonaliased_buffers((void **)&srcbuf, size * threads, (void **)&dstbuf, size * threads, NULL, 0);
STEP("CPU copy", 0.40f);
out->cpu_copy = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_copy);
STEP("CPU read", 0.55f);
out->cpu_read = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fetch);
STEP("CPU write", 0.70f);
out->cpu_write = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fill);
free(poolbuf);
STEP("Latency", 0.85f);
latency_bench(&out->l2_ns, &out->ram_ns);
STEP("Done", 1.0f);
#undef STEP
}
struct bench_ctx {
int phase;
bool is_4gb;
void *pool;
int64_t *src;
int64_t *dst;
};
bench_ctx *bench_begin(void) {
bench_ctx *c = (bench_ctx *)calloc(1, sizeof(bench_ctx));
if (!c)
return NULL;
c->is_4gb = (appletGetAppletType() == AppletType_Application);
c->pool = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
return c;
}
bool bench_step(bench_ctx *c, bench_results_t *out, const char **label, float *frac) {
const int threads = 3;
const int size = SIZE;
switch (c->phase) {
case 0:
gpu_bw_run(c->is_4gb, &out->gpu_copy, &out->gpu_read, &out->gpu_write);
*label = "GPU bandwidth";
*frac = 0.25f;
break;
case 1:
out->cpu_copy = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_copy);
*label = "CPU copy";
*frac = 0.45f;
break;
case 2:
out->cpu_read = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fetch);
*label = "CPU read";
*frac = 0.60f;
break;
case 3:
out->cpu_write = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fill);
*label = "CPU write";
*frac = 0.75f;
break;
case 4:
if (c->pool) {
free(c->pool);
c->pool = NULL;
}
latency_bench(&out->l2_ns, &out->ram_ns);
*label = "Latency";
*frac = 0.95f;
break;
default:
*label = "Done";
*frac = 1.0f;
return false;
}
c->phase++;
return true;
}
void bench_end(bench_ctx *c) {
if (!c)
return;
if (c->pool)
free(c->pool);
free(c);
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t cpu_hz;
uint32_t gpu_hz;
uint32_t mem_hz;
bool is_4gb;
int threads;
} sysinfo_t;
void bench_get_sysinfo(sysinfo_t *out);
typedef struct {
double gpu_copy, gpu_read, gpu_write;
double cpu_copy, cpu_read, cpu_write;
double l2_ns, ram_ns;
} bench_results_t;
typedef void (*bench_progress_fn)(const char *stage, float frac, void *user);
void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user);
typedef struct bench_ctx bench_ctx;
bench_ctx *bench_begin(void);
bool bench_step(bench_ctx *ctx, bench_results_t *out, const char **label, float *frac);
void bench_end(bench_ctx *ctx);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
void BHRTSceneInit();
void BHRTRender();
void BHRTExit();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
void CPURBSceneinit();
void CPURBRender();
void CPURBExit();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
void CPURTSceneinit();
void CPURTRender();
void CPURTExit();

View File

@@ -0,0 +1,770 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <glad/glad.h>
#define GLM_FORCE_PURE
#include "sates.h"
#include "stb_image.h"
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#ifndef ENABLE_NXLINK
#define TRACE(fmt, ...) ((void)0)
#else
#include <unistd.h>
#define TRACE(fmt, ...) printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__)
static int s_nxlinkSock = -1;
static void initNxLink() {
if (R_FAILED(socketInitializeDefault()))
return;
s_nxlinkSock = nxlinkStdio();
if (s_nxlinkSock >= 0)
TRACE("printf output now goes to nxlink server");
else
socketExit();
}
static void deinitNxLink() {
if (s_nxlinkSock >= 0) {
close(s_nxlinkSock);
socketExit();
s_nxlinkSock = -1;
}
}
extern "C" void userAppInit() {
initNxLink();
}
extern "C" void userAppExit() {
deinitNxLink();
}
#endif
static EGLDisplay s_display;
static EGLContext s_context;
static EGLSurface s_surface;
static bool initEgl(NWindow *win) {
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!s_display) {
TRACE("Could not connect to display! error: %d", eglGetError());
goto _fail0;
}
eglInitialize(s_display, nullptr, nullptr);
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
TRACE("Could not set API! error: %d", eglGetError());
goto _fail1;
}
EGLConfig config;
EGLint numConfigs;
static const EGLint framebufferAttributeList[] = { EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_STENCIL_SIZE,
8,
EGL_NONE };
eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs);
if (numConfigs == 0) {
TRACE("No config found! error: %d", eglGetError());
goto _fail1;
}
s_surface = eglCreateWindowSurface(s_display, config, win, nullptr);
if (!s_surface) {
TRACE("Surface creation failed! error: %d", eglGetError());
goto _fail1;
}
static const EGLint contextAttributeList[] = { EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION_KHR,
4,
EGL_CONTEXT_MINOR_VERSION_KHR,
3,
EGL_NONE };
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
if (!s_context) {
TRACE("Context creation failed! error: %d", eglGetError());
goto _fail2;
}
eglMakeCurrent(s_display, s_surface, s_surface, s_context);
return true;
_fail2:
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
_fail1:
eglTerminate(s_display);
s_display = nullptr;
_fail0:
return false;
}
static void deinitEgl() {
if (s_display) {
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (s_context) {
eglDestroyContext(s_display, s_context);
s_context = nullptr;
}
if (s_surface) {
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
}
eglTerminate(s_display);
s_display = nullptr;
}
}
static const char *const text_vs = R"text(
#version 330 core
layout(location=0) in vec2 inPos;
layout(location=1) in vec3 inColor;
out vec3 color;
void main() {
color = inColor;
gl_Position = vec4(inPos, 0.0, 1.0);
}
)text";
static const char *const text_fs = R"text(
#version 330 core
in vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0);
}
)text";
static GLuint s_textProgram = 0;
static GLuint s_textVao = 0;
static GLuint s_textVbo = 0;
static const unsigned char font8x8[11][8] = { { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 }, { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 },
{ 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 }, { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 },
{ 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 }, { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 },
{ 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 }, { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 },
{ 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 }, { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 } };
static GLuint createAndCompileShader(GLenum type, const char *source);
static void initTextRenderer() {
GLuint vsh = createAndCompileShader(GL_VERTEX_SHADER, text_vs);
GLuint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, text_fs);
s_textProgram = glCreateProgram();
glAttachShader(s_textProgram, vsh);
glAttachShader(s_textProgram, fsh);
glLinkProgram(s_textProgram);
glDeleteShader(vsh);
glDeleteShader(fsh);
glGenVertexArrays(1, &s_textVao);
glGenBuffers(1, &s_textVbo);
}
static void drawTextPixel(float x, float y, float size, float r, float g, float b, float *vertexData, int *offset) {
float verts[] = { x, y, r, g, b, x + size, y, r, g, b, x + size, y + size, r, g, b,
x, y, r, g, b, x + size, y + size, r, g, b, x, y + size, r, g, b };
memcpy(&vertexData[*offset], verts, sizeof(verts));
*offset += 30;
}
static void drawChar(char c, float x, float y, float scale, float r, float g, float b, float *vertexData, int *offset) {
int idx = -1;
if (c >= '0' && c <= '9')
idx = c - '0';
else if (c == '.')
idx = 10;
else
return;
const unsigned char *glyph = font8x8[idx];
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (glyph[row] & (1 << col)) {
float px = x + col * scale;
float py = y - row * scale;
drawTextPixel(px, py, scale, r, g, b, vertexData, offset);
}
}
}
}
static void drawText(const char *text, float x, float y, float scale, float r, float g, float b) {
float *vertexData = (float *)malloc(100 * 64 * 6 * 5 * sizeof(float));
int offset = 0;
float cx = x;
while (*text) {
drawChar(*text, cx, y, scale, r, g, b, vertexData, &offset);
cx += 8 * scale;
text++;
}
if (offset > 0) {
glUseProgram(s_textProgram);
glBindVertexArray(s_textVao);
glBindBuffer(GL_ARRAY_BUFFER, s_textVbo);
glBufferData(GL_ARRAY_BUFFER, offset * sizeof(float), vertexData, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, offset / 5);
}
free(vertexData);
}
static void cleanupTextRenderer() {
if (s_textVbo) {
glDeleteBuffers(1, &s_textVbo);
s_textVbo = 0;
}
if (s_textVao) {
glDeleteVertexArrays(1, &s_textVao);
s_textVao = 0;
}
if (s_textProgram) {
glDeleteProgram(s_textProgram);
s_textProgram = 0;
}
}
static void setMesaConfig() {
setenv("EGL_LOG_LEVEL", "debug", 1);
setenv("MESA_VERBOSE", "all", 1);
setenv("NOUVEAU_MESA_DEBUG", "1", 1);
setenv("NV50_PROG_OPTIMIZE", "0", 1);
setenv("NV50_PROG_DEBUG", "1", 1);
setenv("NV50_PROG_CHIPSET", "0x120", 1);
}
static const char *const vertexShaderSource = R"text(
#version 330 core
out vec2 uv;
void main()
{
vec2 pos = vec2(
(gl_VertexID == 2) ? 3.0 : -1.0,
(gl_VertexID == 1) ? 3.0 : -1.0
);
uv = 0.5 * (pos + 1.0);
gl_Position = vec4(pos, 0.0, 1.0);
}
)text";
static const char *const fragmentShaderSource = R"text(
#version 330 core
// Alright so originally this was supposed to be a way more complex shader
// It was supposed to be a proceduraly generated path tracing scene
// Unfortunately, my stupidity knows no bounds, and well over 5000 lines in I decided that I was better off waterboarding myself
// To anyone who even wants to ask, no you don't want to see the original, it was a fucking war crime
// Ask Adam why he thought it was a good idea to copy entire libraries into this shit, and then acted surprised when it didn't work
// so, have a cornel box instead, because I cannot be fucked to make a proper shader file loader, despite needing one soon anyway.
in vec2 uv;
out vec4 fragColor;
uniform vec2 u_resolution;
uniform float u_time;
// Sampler variables
uniform int u_frame;
uniform sampler2D u_prevFrame;
#define WHITE 0
#define RED 1
#define GREEN 2
#define LIGHT 3
#define MIRROR 4
struct Hit {
float dist;
int material;
vec3 normal;
};
float hash(vec2 p){
return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);
}
vec3 rand3(vec3 p){
float f = float(u_frame);
return vec3(
hash(p.xy + f),
hash(p.yz + f*1.37),
hash(p.zx + f*2.17)
) * 2.0 - 1.0;
}
float sdSphere(vec3 p, float r){
return length(p) - r;
}
Hit map(vec3 p)
{
Hit h;
h.dist = 1e9;
h.material = WHITE;
// ---------- mirror sphere ----------
float s = sdSphere(p - vec3(0,1.0,-0.5), 1.0);
if(s < h.dist){
h.dist = s;
h.material = MIRROR;
h.normal = normalize(p - vec3(0,1.0,-0.5));
}
// ---------- room walls (inward planes) ----------
// left (red)
float left = p.x + 2.0;
if(left < h.dist){
h.dist = left;
h.material = RED;
h.normal = vec3(1, 0, 0);
}
// right (green)
float right = 2.0 - p.x;
if(right < h.dist){
h.dist = right;
h.material = GREEN;
h.normal = vec3(-1, 0, 0);
}
// floor
float floor = p.y;
if(floor < h.dist){
h.dist = floor;
h.material = WHITE;
h.normal = vec3(0, 1, 0);
}
// ceiling
float ceil = 4.0 - p.y;
if(ceil < h.dist){
h.dist = ceil;
h.material = WHITE;
h.normal = vec3(0, -1, 0);
}
// back wall
float back = 2.0 - p.z;
if(back < h.dist){
h.dist = back;
h.material = WHITE;
h.normal = vec3(0, 0, -1);
}
vec3 lCenter = vec3(0.0, 3.98, -1.2);
vec3 lSize = vec3(0.9, 0.01, 0.9);
vec3 d = abs(p - lCenter) - lSize;
float light = length(max(d,0.0)) + min(max(d.x,max(d.y,d.z)),0.0);
if(light < h.dist){
h.dist = light;
h.material = LIGHT;
}
return h;
}
Hit raymarch(vec3 ro, vec3 rd)
{
float t = 0.0;
// 80 steps, lower values give better performance, while higher values increase load
// increasing load beyond this only decreases power draw, while lowering results in graphical glitches
for(int i=0;i<80;i++){
vec3 p = ro + rd*t;
Hit h = map(p);
if(h.dist < 0.001){
h.dist = t;
return h;
}
t += h.dist;
if(t > 50.0) break;
}
Hit miss;
miss.dist = -1.0;
return miss;
}
vec3 getColor(int m){
if(m==RED) return vec3(1,0.2,0.2);
if(m==GREEN) return vec3(0.2,1,0.2);
return vec3(0.9);
}
bool isLight(int m){ return m==LIGHT; }
bool isMirror(int m){ return m==MIRROR; }
vec3 trace(vec3 ro, vec3 rd)
{
vec3 color = vec3(0);
vec3 throughput = vec3(1);
// Controls the amount of bounces made by rays in the scene
// reduced to 3 for parody with CPU mode
for(int bounce=0; bounce<3; bounce++)
{
Hit h = raymarch(ro, rd);
// sky fallback
if(h.dist < 0.0){
color += throughput * vec3(0.7,0.8,1.0);
break;
}
vec3 pos = ro + rd*h.dist;
vec3 n = h.normal;
// hit light
if(isLight(h.material)){
color += throughput * vec3(12.0);
break;
}
// mirror bounce
if(isMirror(h.material)){
rd = reflect(rd, n);
}
else{
// diffuse bounce (hemisphere)
vec3 r = rand3(pos + float(bounce) + float(u_frame));
rd = normalize(n + r);
// cosine weighting (energy can only be transfered)
// I mean if you want to change this sure, but you'll get flashbanged.
float cosTheta = max(dot(rd, n), 0.0);
throughput *= getColor(h.material) * cosTheta;
}
ro = pos + n*0.001;
}
// Clamp color values so we don't get a concussion simulator
return color;
}
vec3 pathTrace(vec2 uv)
{
// convert uv to screen space values
vec2 p = uv * 2.0 - 1.0;
p.x *= u_resolution.x / u_resolution.y;
// Camera setup
vec3 ro = vec3(0,2,-6);
vec3 target = vec3(0,2,0);
vec3 forward = normalize(target - ro);
vec3 right = normalize(cross(forward, vec3(0,1,0)));
vec3 up = cross(right, forward);
vec3 rd = normalize(forward + p.x*right + p.y*up);
// Add pixel jittering to reduce noise
vec2 jitter = vec2(
hash(gl_FragCoord.xy + float(u_frame)),
hash(gl_FragCoord.yx + float(u_frame))
) / u_resolution;
return trace(ro, rd + vec3(jitter, 00));
}
void main()
{
//setup output
vec2 uv = gl_FragCoord.xy / u_resolution;
vec3 newSample = pathTrace(uv);
// Accumulation
vec3 prev = texture(u_prevFrame, gl_FragCoord.xy / u_resolution).rgb;
vec3 accumulated;
if(u_frame == 0)
accumulated = newSample;
else
accumulated = (prev *float(u_frame) + newSample) / float(u_frame + 1);
// Output final pixels, send to vertex
fragColor = vec4(accumulated, 1.0);
}
)text";
static GLuint s_program;
static GLuint s_vao, s_vbo;
static GLint resolutionLoc;
static GLint loc_mdlvMtx, loc_projMtx;
static GLint loc_time;
static u64 s_startTicks;
static u64 s_lastFrameTime = 0;
static float s_fps = 0.0f;
static int s_frameCount = 0;
static u64 s_fpsUpdateTime = 0;
static GLuint tex[2], fbo[2];
static GLuint frameLoc, prevframeLoc;
static int frame = 0;
static GLuint createAndCompileShader(GLenum type, const char *source) {
GLint success;
GLchar msg[512];
GLuint handle = glCreateShader(type);
if (!handle) {
TRACE("%u: cannot create shader", type);
return 0;
}
glShaderSource(handle, 1, &source, nullptr);
glCompileShader(handle);
glGetShaderiv(handle, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(handle, sizeof(msg), nullptr, msg);
TRACE("%u: %s\n", type, msg);
glDeleteShader(handle);
return 0;
}
return handle;
}
void GPUPTSceneInit() {
GLint vsh = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
if (!vsh || !fsh) {
TRACE("Shader compile failed — aborting");
return;
}
s_program = glCreateProgram();
glViewport(0, 0, 1280, 720);
glAttachShader(s_program, vsh);
glAttachShader(s_program, fsh);
glBindFragDataLocation(s_program, 0, "fragColor");
glLinkProgram(s_program);
frameLoc = glGetUniformLocation(s_program, "u_frame");
prevframeLoc = glGetUniformLocation(s_program, "u_prevFrame");
resolutionLoc = glGetUniformLocation(s_program, "u_resolution");
loc_time = glGetUniformLocation(s_program, "u_time");
GLint success;
glGetProgramiv(s_program, GL_LINK_STATUS, &success);
if (!success) {
char buf[512];
glGetProgramInfoLog(s_program, sizeof(buf), nullptr, buf);
TRACE("Link error: %s", buf);
}
glDeleteShader(vsh);
glDeleteShader(fsh);
loc_mdlvMtx = glGetUniformLocation(s_program, "mdlvMtx");
loc_projMtx = glGetUniformLocation(s_program, "projMtx");
loc_time = glGetUniformLocation(s_program, "u_time");
static float vertices[] = {
-1.0f, -1.0f, 3.0f, -1.0f, -1.0f, 3.0f,
};
glGenTextures(2, tex);
glGenFramebuffers(2, fbo);
for (int i = 0; i < 2; i++) {
glBindTexture(GL_TEXTURE_2D, tex[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1280, 720, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex[i], 0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
glGenVertexArrays(1, &s_vao);
glGenBuffers(1, &s_vbo);
glBindVertexArray(s_vao);
glBindBuffer(GL_ARRAY_BUFFER, s_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
glEnable(GL_FRAMEBUFFER_SRGB);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(s_program);
auto projMtx = glm::perspective(glm::radians(40.0f), 1280.0f / 720.0f, 0.01f, 1000.0f);
glUniformMatrix4fv(loc_projMtx, 1, GL_FALSE, glm::value_ptr(projMtx));
s_startTicks = armGetSystemTick();
s_lastFrameTime = s_startTicks;
s_fpsUpdateTime = s_startTicks;
s_frameCount = 0;
initTextRenderer();
}
float getTime2() {
u64 elapsed = armGetSystemTick() - s_startTicks;
return (elapsed * 625 / 12) / 2000000000.0;
}
void GPUPTRender() {
static int X = 0;
static int Y = 1;
u64 currentTime = armGetSystemTick();
s_frameCount++;
u64 timeSinceUpdate = currentTime - s_fpsUpdateTime;
float secondsSinceUpdate = (timeSinceUpdate * 625.0f / 12.0f) / 1000000000.0f;
if (secondsSinceUpdate >= 0.01f) {
s_fps = s_frameCount / secondsSinceUpdate;
s_frameCount = 0;
s_fpsUpdateTime = currentTime;
}
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(s_program);
glUniform1f(loc_time, getTime2());
glUniform2f(resolutionLoc, 1280.0f, 720.0f);
glBindVertexArray(s_vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex[X]);
glUniform1i(prevframeLoc, 0);
glUniform1i(frameLoc, frame);
glBindFramebuffer(GL_FRAMEBUFFER, fbo[Y]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[Y]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, 1280, 720, 0, 0, 1280, 720, GL_COLOR_BUFFER_BIT, GL_NEAREST);
if (frame == 0) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo[X]);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
std::swap(X, Y);
frame++;
glBindVertexArray(0);
char fpsText[32];
snprintf(fpsText, sizeof(fpsText), "%.3f", s_fps);
drawText(fpsText, -0.95f, 0.90f, 0.02f, 1.0f, 0.0f, 0.0f);
char sampleText[64];
snprintf(sampleText, sizeof(sampleText), "Samples: %d", frame);
drawText(sampleText, -0.95f, 0.90f, 0.02f, 1.0f, 0.0f, 0.0f);
}
void GPUPTExit() {
cleanupTextRenderer();
glDeleteBuffers(1, &s_vbo);
glDeleteVertexArrays(1, &s_vao);
glDeleteProgram(s_program);
frame = 0;
}
int GPUPTMain(int argc, char *argv[]) {
setMesaConfig();
if (!initEgl(nwindowGetDefault()))
return EXIT_FAILURE;
gladLoadGLLoader((GLADloadproc)eglGetProcAddress);
GPUPTSceneInit();
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
PadState pad;
padInitializeDefault(&pad);
while (appletMainLoop()) {
padUpdate(&pad);
u32 kDown = padGetButtonsDown(&pad);
if (kDown & HidNpadButton_B) {
GPUPTExit();
deinitEgl();
state = STATE_MENU;
return 0;
}
GPUPTRender();
eglSwapBuffers(s_display, s_surface);
}
GPUPTExit();
deinitEgl();
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,4 @@
void GPUPTSceneInit();
void GPUPTRender();
void GPUPTExit();
void getTime2();

View File

@@ -0,0 +1,691 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <glad/glad.h>
#define GLM_FORCE_PURE
#include "fur_png.h"
#include "noise_png.h"
#include "sates.h"
#include "stb_image.h"
#include "wall_png.h"
#include "wunk_png.h"
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#ifndef ENABLE_NXLINK
#define TRACE(fmt, ...) ((void)0)
#else
#include <unistd.h>
#define TRACE(fmt, ...) printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__)
static int s_nxlinkSock = -1;
static void initNxLink() {
if (R_FAILED(socketInitializeDefault()))
return;
s_nxlinkSock = nxlinkStdio();
if (s_nxlinkSock >= 0)
TRACE("printf output now goes to nxlink server");
else
socketExit();
}
static void deinitNxLink() {
if (s_nxlinkSock >= 0) {
close(s_nxlinkSock);
socketExit();
s_nxlinkSock = -1;
}
}
extern "C" void userAppInit() {
initNxLink();
}
extern "C" void userAppExit() {
deinitNxLink();
}
#endif
static EGLDisplay s_display;
static EGLContext s_context;
static EGLSurface s_surface;
static bool initEgl(NWindow *win) {
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!s_display) {
TRACE("Could not connect to display! error: %d", eglGetError());
goto _fail0;
}
eglInitialize(s_display, nullptr, nullptr);
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
TRACE("Could not set API! error: %d", eglGetError());
goto _fail1;
}
EGLConfig config;
EGLint numConfigs;
static const EGLint framebufferAttributeList[] = { EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_STENCIL_SIZE,
8,
EGL_NONE };
eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs);
if (numConfigs == 0) {
TRACE("No config found! error: %d", eglGetError());
goto _fail1;
}
s_surface = eglCreateWindowSurface(s_display, config, win, nullptr);
if (!s_surface) {
TRACE("Surface creation failed! error: %d", eglGetError());
goto _fail1;
}
static const EGLint contextAttributeList[] = { EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION_KHR,
4,
EGL_CONTEXT_MINOR_VERSION_KHR,
3,
EGL_NONE };
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
if (!s_context) {
TRACE("Context creation failed! error: %d", eglGetError());
goto _fail2;
}
eglMakeCurrent(s_display, s_surface, s_surface, s_context);
return true;
_fail2:
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
_fail1:
eglTerminate(s_display);
s_display = nullptr;
_fail0:
return false;
}
static void deinitEgl() {
if (s_display) {
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (s_context) {
eglDestroyContext(s_display, s_context);
s_context = nullptr;
}
if (s_surface) {
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
}
eglTerminate(s_display);
s_display = nullptr;
}
}
static const char *const text_vs = R"text(
#version 330 core
layout(location=0) in vec2 inPos;
layout(location=1) in vec3 inColor;
out vec3 color;
void main() {
color = inColor;
gl_Position = vec4(inPos, 0.0, 1.0);
}
)text";
static const char *const text_fs = R"text(
#version 330 core
in vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0);
}
)text";
static GLuint s_textProgram = 0;
static GLuint s_textVao = 0;
static GLuint s_textVbo = 0;
static const unsigned char font8x8[11][8] = { { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 }, { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 },
{ 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 }, { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 },
{ 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 }, { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 },
{ 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 }, { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 },
{ 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 }, { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 } };
static GLuint createAndCompileShader(GLenum type, const char *source);
static void initTextRenderer() {
GLuint vsh = createAndCompileShader(GL_VERTEX_SHADER, text_vs);
GLuint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, text_fs);
s_textProgram = glCreateProgram();
glAttachShader(s_textProgram, vsh);
glAttachShader(s_textProgram, fsh);
glLinkProgram(s_textProgram);
glDeleteShader(vsh);
glDeleteShader(fsh);
glGenVertexArrays(1, &s_textVao);
glGenBuffers(1, &s_textVbo);
}
static void drawTextPixel(float x, float y, float size, float r, float g, float b, float *vertexData, int *offset) {
float verts[] = { x, y, r, g, b, x + size, y, r, g, b, x + size, y + size, r, g, b,
x, y, r, g, b, x + size, y + size, r, g, b, x, y + size, r, g, b };
memcpy(&vertexData[*offset], verts, sizeof(verts));
*offset += 30;
}
static void drawChar(char c, float x, float y, float scale, float r, float g, float b, float *vertexData, int *offset) {
int idx = -1;
if (c >= '0' && c <= '9')
idx = c - '0';
else if (c == '.')
idx = 10;
else
return;
const unsigned char *glyph = font8x8[idx];
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (glyph[row] & (1 << col)) {
float px = x + col * scale;
float py = y - row * scale;
drawTextPixel(px, py, scale, r, g, b, vertexData, offset);
}
}
}
}
static void drawText(const char *text, float x, float y, float scale, float r, float g, float b) {
float *vertexData = (float *)malloc(100 * 64 * 6 * 5 * sizeof(float));
int offset = 0;
float cx = x;
while (*text) {
drawChar(*text, cx, y, scale, r, g, b, vertexData, &offset);
cx += 8 * scale;
text++;
}
if (offset > 0) {
glUseProgram(s_textProgram);
glBindVertexArray(s_textVao);
glBindBuffer(GL_ARRAY_BUFFER, s_textVbo);
glBufferData(GL_ARRAY_BUFFER, offset * sizeof(float), vertexData, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, offset / 5);
}
free(vertexData);
}
static void cleanupTextRenderer() {
if (s_textVbo) {
glDeleteBuffers(1, &s_textVbo);
s_textVbo = 0;
}
if (s_textVao) {
glDeleteVertexArrays(1, &s_textVao);
s_textVao = 0;
}
if (s_textProgram) {
glDeleteProgram(s_textProgram);
s_textProgram = 0;
}
}
static void setMesaConfig() {
}
static const char *const vertexShaderSource = R"text(
#version 330 core
out vec2 v_uv;
void main() {
vec2 pos = vec2(
(gl_VertexID == 1) ? 3.0 : -1.0,
(gl_VertexID == 2) ? 3.0 : -1.0
);
v_uv = pos * 0.5 + 0.5;
gl_Position = vec4(pos, 0.0, 1.0);
}
)text";
static const char *const fragmentShaderSource = R"text(
#version 330 core
out vec4 fragColor;
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D u_texture1;
uniform sampler2D u_texture2;
uniform sampler2D u_texture3;
uniform sampler2D u_texture4;
const float PI = 3.1416;
const float TAU = 2 * PI;
float displace(vec3 p, sampler2D tex) {
float s = 4.5;
float u = s / TAU * atan(p.y / p.x);
float v = sign(p.z) / TAU *
acos((p.z * p.z * sqrt(s * s + 1) + sqrt(1 - p.z * p.z * s * s)) / (p.z * p.z + 1));
vec2 uv = 2.0 * vec2(u, v);
float disp = texture(tex, uv).r;
return disp * 0.06;
}
mat2 rot2D(float a) {
float sa = sin(a);
float ca = cos(a);
return mat2(ca, sa, -sa, ca);
}
void rotate(inout vec3 p) {
p.xy *= rot2D(sin(u_time * 0.8) * 0.25);
p.yz *= rot2D(sin(u_time * 0.7) * 0.2);
}
float map(vec3 p) {
float dist = length(vec2(length(p.xy) - 0.6, p.z)) - 0.22;
return dist * 0.7;
}
vec3 getNormal(vec3 p) {
vec2 e = vec2(0.01, 0.0);
vec3 n = vec3(map(p)) - vec3(map(p - e.xyy), map(p - e.yxy), map(p - e.yyx));
return normalize(n);
}
float rayMarch(vec3 ro, vec3 rd) {
float dist = 0.0;
for (int i = 0; i < 32; i++) {
vec3 p = ro + dist * rd;
rotate(p);
float hit = map(p);
dist += hit;
// displace
dist -= displace(0.5 * p, u_texture2);
vec2 uv = fract(p.xy * 0.317);
float t39 = texture(u_texture1, uv.yx).r;
float texNoise = texture(u_texture2, uv).r;
float t3 = texture(u_texture3, uv * 0.73).r;
float car = texture(u_texture4, uv * 0.37).r;
dist += (texNoise + t39 + t3 + car) * 1e-4;
dist += (texNoise + t39 + t3 + car) * 1e-5;
dist += (texNoise + t39 + t3 + car) * 1e-6;
if (dist > 100.0 || abs(hit) < 0.0001) break;
}
return dist;
}
vec3 triPlanar(sampler2D tex, vec3 p, vec3 normal) {
normal = abs(normal);
normal = pow(normal, vec3(15));
normal /= normal.x + normal.y + normal.z;
p = p * 0.5 + 0.5;
return (texture(tex, p.xy) * normal.z +
texture(tex, p.xz) * normal.y +
texture(tex, p.yz) * normal.x).rgb;
}
vec3 render(vec2 offset) {
vec2 uv = (4.0 * (gl_FragCoord.xy + offset) - u_resolution.xy) / u_resolution.y;
vec3 col = vec3(0);
vec3 ro = vec3(0, 0, -1.0);
vec3 rd = normalize(vec3(uv, 1.0));
// return to normal rendering path
float dist = rayMarch(ro, rd);
if (dist < 100.0) {
vec3 p = ro + dist * rd;
rotate(p);
col += triPlanar(u_texture1, p * 1.0, getNormal(p));
} else {
float phi = atan(uv.y, uv.x);
float rho = length(uv) + 0.2;
phi += sin(0.3 * rho - 0.5 * u_time);
float h = sin(8.0 * phi) * 0.5 + 0.5;
vec2 st;
st.x = 3.0 * phi / PI;
st.y = u_time * 0.5 + PI / (rho + 0.1 * smoothstep(0.45, 0.5, h));
col += texture(u_texture3, st).rgb;
float occ = smoothstep(0.0, 0.45, h) - smoothstep(0.5, 1.0, h);
col *= 1.0 - 0.45 * occ * rho;
col *= rho;
}
return col;
}
vec3 renderAAx4() {
vec4 e = vec4(0.125, -0.125, 0.375, -0.375);
vec3 colAA = render(e.xz) + render(e.yw) + render(e.wx) + render(e.zy);
return colAA /= 4.0;
}
void main() {
vec3 color = renderAAx4();
fragColor = vec4(color, 1.0);
}
)text";
static GLuint s_program;
static GLuint s_vao, s_vbo;
static GLint resolutionLoc;
static GLuint tex1;
static GLuint tex2;
static GLuint tex3;
static GLuint tex4;
static GLint loc_mdlvMtx, loc_projMtx;
static GLint loc_lightPos, loc_ambient, loc_diffuse, loc_specular, loc_tex_diffuse;
static GLint loc_time;
static u64 s_startTicks;
static u64 s_lastFrameTime = 0;
static float s_fps = 0.0f;
static int s_frameCount = 0;
static u64 s_fpsUpdateTime = 0;
static GLuint createAndCompileShader(GLenum type, const char *source) {
GLint success;
GLchar msg[512];
GLuint handle = glCreateShader(type);
if (!handle) {
TRACE("%u: cannot create shader", type);
return 0;
}
glShaderSource(handle, 1, &source, nullptr);
glCompileShader(handle);
glGetShaderiv(handle, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(handle, sizeof(msg), nullptr, msg);
TRACE("%u: %s\n", type, msg);
glDeleteShader(handle);
return 0;
}
return handle;
}
void frRamSceneInit() {
GLint vsh = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
s_program = glCreateProgram();
glAttachShader(s_program, vsh);
glAttachShader(s_program, fsh);
glLinkProgram(s_program);
resolutionLoc = glGetUniformLocation(s_program, "u_resolution");
GLuint tex1Loc = glGetUniformLocation(s_program, "u_texture1");
GLuint tex2Loc = glGetUniformLocation(s_program, "u_texture2");
GLuint tex3Loc = glGetUniformLocation(s_program, "u_texture3");
GLuint tex4Loc = glGetUniformLocation(s_program, "u_texture4");
loc_time = glGetUniformLocation(s_program, "u_time");
GLint success;
glGetProgramiv(s_program, GL_LINK_STATUS, &success);
if (!success) {
char buf[512];
glGetProgramInfoLog(s_program, sizeof(buf), nullptr, buf);
TRACE("Link error: %s", buf);
}
glDeleteShader(vsh);
glDeleteShader(fsh);
loc_mdlvMtx = glGetUniformLocation(s_program, "mdlvMtx");
loc_projMtx = glGetUniformLocation(s_program, "projMtx");
loc_lightPos = glGetUniformLocation(s_program, "lightPos");
loc_ambient = glGetUniformLocation(s_program, "ambient");
loc_diffuse = glGetUniformLocation(s_program, "diffuse");
loc_specular = glGetUniformLocation(s_program, "specular");
loc_tex_diffuse = glGetUniformLocation(s_program, "tex_diffuse");
loc_time = glGetUniformLocation(s_program, "u_time");
struct Vertex {
float position[3];
float color[3];
glm::vec2 texcoord;
glm::vec3 normal;
};
static const Vertex vertices[] = {
{ { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
{ { 0.0f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f } },
};
glGenVertexArrays(1, &s_vao);
glGenBuffers(1, &s_vbo);
glBindVertexArray(s_vao);
glBindBuffer(GL_ARRAY_BUFFER, s_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, position));
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, color));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, texcoord));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, normal));
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glGenerateMipmap(GL_TEXTURE_2D);
int width, height, nchan;
stbi_set_flip_vertically_on_load(true);
glGenTextures(1, &tex1);
glBindTexture(GL_TEXTURE_2D, tex1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
stbi_uc *img = stbi_load_from_memory((const stbi_uc *)fur_png, fur_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glGenTextures(1, &tex2);
glBindTexture(GL_TEXTURE_2D, tex2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
img = stbi_load_from_memory((const stbi_uc *)noise_png, noise_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glGenTextures(1, &tex3);
glBindTexture(GL_TEXTURE_2D, tex3);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
img = stbi_load_from_memory((const stbi_uc *)wall_png, wall_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glGenTextures(1, &tex4);
glBindTexture(GL_TEXTURE_2D, tex4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
img = stbi_load_from_memory((const stbi_uc *)wunk_png, wunk_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glUseProgram(s_program);
auto projMtx = glm::perspective(glm::radians(40.0f), 1280.0f / 720.0f, 0.01f, 500.0f);
glUniformMatrix4fv(loc_projMtx, 1, GL_FALSE, glm::value_ptr(projMtx));
glUniform4f(loc_lightPos, 0.0f, 0.0f, 0.5f, 1.0f);
glUniform3f(loc_ambient, 0.1f, 0.1f, 0.1f);
glUniform3f(loc_diffuse, 0.4f, 0.4f, 0.4f);
glUniform4f(loc_specular, 0.5f, 0.5f, 0.5f, 20.0f);
s_startTicks = armGetSystemTick();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);
glUniform1i(tex1Loc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex2);
glUniform1i(tex2Loc, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, tex3);
glUniform1i(tex3Loc, 2);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, tex4);
glUniform1i(tex4Loc, 3);
s_lastFrameTime = s_startTicks;
s_fpsUpdateTime = s_startTicks;
s_frameCount = 0;
initTextRenderer();
}
float getTime1() {
u64 elapsed = armGetSystemTick() - s_startTicks;
return (elapsed * 625 / 12) / 2000000000.0;
}
void frRamRender() {
u64 currentTime = armGetSystemTick();
s_frameCount++;
u64 timeSinceUpdate = currentTime - s_fpsUpdateTime;
float secondsSinceUpdate = (timeSinceUpdate * 625.0f / 12.0f) / 1000000000.0f;
if (secondsSinceUpdate >= 0.01f) {
s_fps = s_frameCount / secondsSinceUpdate;
s_frameCount = 0;
s_fpsUpdateTime = currentTime;
}
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(s_program);
glViewport(0, 0, 1280, 720);
glUniform1f(loc_time, getTime1());
glUniform2f(resolutionLoc, 640.0f, 360.0f);
glBindVertexArray(s_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
char fpsText[32];
snprintf(fpsText, sizeof(fpsText), "%.3f", s_fps);
drawText(fpsText, -0.95f, 0.90f, 0.02f, 1.0f, 0.0f, 0.0f);
}
void frRamExit() {
cleanupTextRenderer();
glDeleteBuffers(1, &s_vbo);
glDeleteVertexArrays(1, &s_vao);
glDeleteProgram(s_program);
}
int frRamMain(int argc, char *argv[]) {
setMesaConfig();
if (!initEgl(nwindowGetDefault()))
return EXIT_FAILURE;
gladLoadGLLoader((GLADloadproc)eglGetProcAddress);
frRamSceneInit();
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
PadState pad;
padInitializeDefault(&pad);
while (appletMainLoop()) {
padUpdate(&pad);
u32 kDown = padGetButtonsDown(&pad);
if (kDown & HidNpadButton_B) {
frRamExit();
deinitEgl();
state = STATE_MENU;
return 0;
}
frRamRender();
eglSwapBuffers(s_display, s_surface);
}
frRamExit();
deinitEgl();
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,4 @@
void frRamSceneInit();
void frRamRender();
void frRamExit();
void getTime1();

View File

@@ -0,0 +1,682 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <glad/glad.h>
#define GLM_FORCE_PURE
#include "fur_png.h"
#include "noise_png.h"
#include "sates.h"
#include "stb_image.h"
#include "wall_png.h"
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#ifndef ENABLE_NXLINK
#define TRACE(fmt, ...) ((void)0)
#else
#include <unistd.h>
#define TRACE(fmt, ...) printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__)
static int s_nxlinkSock = -1;
static void initNxLink() {
if (R_FAILED(socketInitializeDefault()))
return;
s_nxlinkSock = nxlinkStdio();
if (s_nxlinkSock >= 0)
TRACE("printf output now goes to nxlink server");
else
socketExit();
}
static void deinitNxLink() {
if (s_nxlinkSock >= 0) {
close(s_nxlinkSock);
socketExit();
s_nxlinkSock = -1;
}
}
extern "C" void userAppInit() {
initNxLink();
}
extern "C" void userAppExit() {
deinitNxLink();
}
#endif
static EGLDisplay s_display;
static EGLContext s_context;
static EGLSurface s_surface;
static bool initEgl(NWindow *win) {
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!s_display) {
TRACE("Could not connect to display! error: %d", eglGetError());
goto _fail0;
}
eglInitialize(s_display, nullptr, nullptr);
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
TRACE("Could not set API! error: %d", eglGetError());
goto _fail1;
}
EGLConfig config;
EGLint numConfigs;
static const EGLint framebufferAttributeList[] = { EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_STENCIL_SIZE,
8,
EGL_NONE };
eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs);
if (numConfigs == 0) {
TRACE("No config found! error: %d", eglGetError());
goto _fail1;
}
s_surface = eglCreateWindowSurface(s_display, config, win, nullptr);
if (!s_surface) {
TRACE("Surface creation failed! error: %d", eglGetError());
goto _fail1;
}
static const EGLint contextAttributeList[] = { EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION_KHR,
4,
EGL_CONTEXT_MINOR_VERSION_KHR,
3,
EGL_NONE };
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
if (!s_context) {
TRACE("Context creation failed! error: %d", eglGetError());
goto _fail2;
}
eglMakeCurrent(s_display, s_surface, s_surface, s_context);
eglSwapInterval(s_display, 0);
return true;
_fail2:
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
_fail1:
eglTerminate(s_display);
s_display = nullptr;
_fail0:
return false;
}
static void deinitEgl() {
if (s_display) {
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (s_context) {
eglDestroyContext(s_display, s_context);
s_context = nullptr;
}
if (s_surface) {
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
}
eglTerminate(s_display);
s_display = nullptr;
}
}
static const char *const text_vs = R"text(
#version 330 core
layout(location=0) in vec2 inPos;
layout(location=1) in vec3 inColor;
out vec3 color;
void main() {
color = inColor;
gl_Position = vec4(inPos, 0.0, 1.0);
}
)text";
static const char *const text_fs = R"text(
#version 330 core
in vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0);
}
)text";
static GLuint s_textProgram = 0;
static GLuint s_textVao = 0;
static GLuint s_textVbo = 0;
static const unsigned char font8x8[11][8] = { { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 }, { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 },
{ 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 }, { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 },
{ 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 }, { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 },
{ 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 }, { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 },
{ 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 }, { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 } };
GLuint createAndCompileShader(GLenum type, const char *source);
static void initTextRenderer() {
GLuint vsh = createAndCompileShader(GL_VERTEX_SHADER, text_vs);
GLuint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, text_fs);
s_textProgram = glCreateProgram();
glAttachShader(s_textProgram, vsh);
glAttachShader(s_textProgram, fsh);
glLinkProgram(s_textProgram);
glDeleteShader(vsh);
glDeleteShader(fsh);
glGenVertexArrays(1, &s_textVao);
glGenBuffers(1, &s_textVbo);
}
static void drawTextPixel(float x, float y, float size, float r, float g, float b, float *vertexData, int *offset) {
float verts[] = { x, y, r, g, b, x + size, y, r, g, b, x + size, y + size, r, g, b,
x, y, r, g, b, x + size, y + size, r, g, b, x, y + size, r, g, b };
memcpy(&vertexData[*offset], verts, sizeof(verts));
*offset += 30;
}
static void drawChar(char c, float x, float y, float scale, float r, float g, float b, float *vertexData, int *offset) {
int idx = -1;
if (c >= '0' && c <= '9')
idx = c - '0';
else if (c == '.')
idx = 10;
else
return;
const unsigned char *glyph = font8x8[idx];
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (glyph[row] & (1 << col)) {
float px = x + col * scale;
float py = y - row * scale;
drawTextPixel(px, py, scale, r, g, b, vertexData, offset);
}
}
}
}
static void drawText(const char *text, float x, float y, float scale, float r, float g, float b) {
float *vertexData = (float *)malloc(100 * 64 * 6 * 5 * sizeof(float));
int offset = 0;
float cx = x;
while (*text) {
drawChar(*text, cx, y, scale, r, g, b, vertexData, &offset);
cx += 8 * scale;
text++;
}
if (offset > 0) {
glUseProgram(s_textProgram);
glBindVertexArray(s_textVao);
glBindBuffer(GL_ARRAY_BUFFER, s_textVbo);
glBufferData(GL_ARRAY_BUFFER, offset * sizeof(float), vertexData, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, offset / 5);
}
free(vertexData);
}
static void cleanupTextRenderer() {
if (s_textVbo) {
glDeleteBuffers(1, &s_textVbo);
s_textVbo = 0;
}
if (s_textVao) {
glDeleteVertexArrays(1, &s_textVao);
s_textVao = 0;
}
if (s_textProgram) {
glDeleteProgram(s_textProgram);
s_textProgram = 0;
}
}
static void setMesaConfig() {
setenv("MESA_NO_ERROR", "1", 1);
setenv("NV50_PROG_CHIPSET", "0", 1);
}
static const char *const vertexShaderSource = R"text(
#version 330 core
out vec2 v_uv;
void main() {
vec2 pos = vec2(
(gl_VertexID == 1) ? 3.0 : -1.0,
(gl_VertexID == 2) ? 3.0 : -1.0
);
v_uv = pos * 0.5 + 0.5;
gl_Position = vec4(pos, 0.0, 1.0);
}
)text";
static const char *const fragmentShaderSource = R"text(
#version 330 core
out vec4 fragColor;
uniform vec2 u_resolution;
uniform float u_time;
uniform sampler2D u_texture1;
uniform sampler2D u_texture2;
uniform sampler2D u_texture3;
const float PI = 3.1416;
const float TAU = 2 * PI;
float displace(vec3 p, sampler2D tex) {
float s = 4.5;
float u = s / TAU * atan(p.y / p.x);
float v = sign(p.z) / TAU *
acos((p.z * p.z * sqrt(s * s + 1) + sqrt(1 - p.z * p.z * s * s)) / (p.z * p.z + 1));
vec2 uv = 2.0 * vec2(u, v);
float disp = texture(tex, uv).r;
return disp * 0.06;
}
mat2 rot2D(float a) {
float sa = sin(a);
float ca = cos(a);
return mat2(ca, sa, -sa, ca);
}
void rotate(inout vec3 p) {
p.xy *= rot2D(sin(u_time * 0.8) * 0.25);
p.yz *= rot2D(sin(u_time * 0.7) * 0.2);
}
float map(vec3 p) {
float dist = length(vec2(length(p.xy) - 0.6, p.z)) - 0.22;
return dist * 0.7;
}
vec3 getNormal(vec3 p) {
vec2 e = vec2(0.01, 0.0);
vec3 n = vec3(map(p)) - vec3(map(p - e.xyy), map(p - e.yxy), map(p - e.yyx));
return normalize(n);
}
float rayMarch(vec3 ro, vec3 rd) {
float dist = 0.0;
for (int i = 0; i < 48; i++) {
vec3 p = ro + dist * rd;
rotate(p);
float hit = map(p);
dist += hit;
// displace
dist -= displace(0.5 * p, u_texture2);
// Saturate FP32
vec3 q = p;
q = q * 1.37 + 0.13;
q = q * q - 0.17;
q = q * 0.91 + q.yzx * 0.09;
dist += dot(q, q) * 1e-5;
// Register stress
vec4 r0 = vec4(p, dist);
vec4 r1 = sin(r0 * 3.1);
vec4 r2 = cos(r1 * 2.7);
vec4 r3 = r2 * r1;
vec4 r4 = normalize(r3);
dist += dot(r4, vec4(1e-5));
if (dist > 100.0 || abs(hit) < 0.0001) break;
}
return dist;
}
vec3 triPlanar(sampler2D tex, vec3 p, vec3 normal) {
normal = abs(normal);
normal = pow(normal, vec3(15));
normal /= normal.x + normal.y + normal.z;
p = p * 0.5 + 0.5;
return (texture(tex, p.xy) * normal.z +
texture(tex, p.xz) * normal.y +
texture(tex, p.yz) * normal.x).rgb;
}
vec3 render(vec2 offset) {
vec2 uv = (2.0 * (gl_FragCoord.xy + offset) - u_resolution.xy) / u_resolution.y;
vec3 col = vec3(0);
vec3 ro = vec3(0, 0, -1.0);
vec3 rd = normalize(vec3(uv, 1.0));
// return to normal rendering path
float dist = rayMarch(ro, rd);
if (dist < 100.0) {
vec3 p = ro + dist * rd;
rotate(p);
col += triPlanar(u_texture1, p * 1.0, getNormal(p));
} else {
float phi = atan(uv.y, uv.x);
float rho = length(uv) + 0.2;
phi += sin(0.3 * rho - 0.5 * u_time);
float h = sin(8.0 * phi) * 0.5 + 0.5;
vec2 st;
st.x = 3.0 * phi / PI;
st.y = u_time * 0.5 + PI / (rho + 0.1 * smoothstep(0.45, 0.5, h));
col += texture(u_texture3, st).rgb;
float occ = smoothstep(0.0, 0.45, h) - smoothstep(0.5, 1.0, h);
col *= 1.0 - 0.45 * occ * rho;
col *= rho;
}
return col;
}
vec3 renderAAx4() {
vec4 e = vec4(0.125, -0.125, 0.375, -0.375);
vec3 colAA = render(e.xz) + render(e.yw) + render(e.wx) + render(e.zy);
return colAA /= 4.0;
}
void main() {
vec3 color = renderAAx4();
fragColor = vec4(color, 1.0);
}
)text";
static GLuint s_program;
static GLuint s_vao, s_vbo;
static GLint resolutionLoc;
static GLuint tex1;
static GLuint tex2;
static GLuint tex3;
static GLint loc_mdlvMtx, loc_projMtx;
static GLint loc_lightPos, loc_ambient, loc_diffuse, loc_specular, loc_tex_diffuse;
static GLint loc_time;
static u64 s_startTicks;
static u64 s_lastFrameTime = 0;
static float s_fps = 0.0f;
static int s_frameCount = 0;
static u64 s_fpsUpdateTime = 0;
GLuint createAndCompileShader(GLenum type, const char *source) {
GLint success;
GLchar msg[512];
GLuint handle = glCreateShader(type);
if (!handle) {
TRACE("%u: cannot create shader", type);
return 0;
}
glShaderSource(handle, 1, &source, nullptr);
glCompileShader(handle);
glGetShaderiv(handle, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(handle, sizeof(msg), nullptr, msg);
TRACE("%u: %s\n", type, msg);
glDeleteShader(handle);
return 0;
}
return handle;
}
void frSceneInit() {
GLint vsh = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
s_program = glCreateProgram();
glAttachShader(s_program, vsh);
glAttachShader(s_program, fsh);
glLinkProgram(s_program);
resolutionLoc = glGetUniformLocation(s_program, "u_resolution");
GLuint tex1Loc = glGetUniformLocation(s_program, "u_texture1");
GLuint tex2Loc = glGetUniformLocation(s_program, "u_texture2");
GLuint tex3Loc = glGetUniformLocation(s_program, "u_texture3");
loc_time = glGetUniformLocation(s_program, "u_time");
GLint success;
glGetProgramiv(s_program, GL_LINK_STATUS, &success);
if (!success) {
char buf[512];
glGetProgramInfoLog(s_program, sizeof(buf), nullptr, buf);
TRACE("Link error: %s", buf);
}
glDeleteShader(vsh);
glDeleteShader(fsh);
loc_mdlvMtx = glGetUniformLocation(s_program, "mdlvMtx");
loc_projMtx = glGetUniformLocation(s_program, "projMtx");
loc_lightPos = glGetUniformLocation(s_program, "lightPos");
loc_ambient = glGetUniformLocation(s_program, "ambient");
loc_diffuse = glGetUniformLocation(s_program, "diffuse");
loc_specular = glGetUniformLocation(s_program, "specular");
loc_tex_diffuse = glGetUniformLocation(s_program, "tex_diffuse");
loc_time = glGetUniformLocation(s_program, "u_time");
struct Vertex {
float position[3];
float color[3];
glm::vec2 texcoord;
glm::vec3 normal;
};
static const Vertex vertices[] = {
{ { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
{ { 0.0f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f } },
};
glGenVertexArrays(1, &s_vao);
glGenBuffers(1, &s_vbo);
glBindVertexArray(s_vao);
glBindBuffer(GL_ARRAY_BUFFER, s_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, position));
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, color));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, texcoord));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, normal));
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glGenerateMipmap(GL_TEXTURE_2D);
int width, height, nchan;
stbi_set_flip_vertically_on_load(true);
glGenTextures(1, &tex1);
glBindTexture(GL_TEXTURE_2D, tex1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
stbi_uc *img = stbi_load_from_memory((const stbi_uc *)fur_png, fur_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glGenTextures(1, &tex2);
glBindTexture(GL_TEXTURE_2D, tex2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
img = stbi_load_from_memory((const stbi_uc *)noise_png, noise_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glGenTextures(1, &tex3);
glBindTexture(GL_TEXTURE_2D, tex3);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
img = stbi_load_from_memory((const stbi_uc *)wall_png, wall_png_size, &width, &height, &nchan, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
stbi_image_free(img);
glUseProgram(s_program);
auto projMtx = glm::perspective(glm::radians(40.0f), 1280.0f / 720.0f, 0.01f, 1000.0f);
glUniformMatrix4fv(loc_projMtx, 1, GL_FALSE, glm::value_ptr(projMtx));
glUniform4f(loc_lightPos, 0.0f, 0.0f, 0.5f, 1.0f);
glUniform3f(loc_ambient, 0.1f, 0.1f, 0.1f);
glUniform3f(loc_diffuse, 0.4f, 0.4f, 0.4f);
glUniform4f(loc_specular, 0.5f, 0.5f, 0.5f, 20.0f);
s_startTicks = armGetSystemTick();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);
glUniform1i(tex1Loc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex2);
glUniform1i(tex2Loc, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, tex3);
glUniform1i(tex3Loc, 2);
s_lastFrameTime = s_startTicks;
s_fpsUpdateTime = s_startTicks;
s_frameCount = 0;
initTextRenderer();
}
float getTime() {
u64 elapsed = armGetSystemTick() - s_startTicks;
return (elapsed * 625 / 12) / 2000000000.0;
}
void frRender() {
u64 currentTime = armGetSystemTick();
s_frameCount++;
u64 timeSinceUpdate = currentTime - s_fpsUpdateTime;
float secondsSinceUpdate = (timeSinceUpdate * 625.0f / 12.0f) / 1000000000.0f;
if (secondsSinceUpdate >= 0.01f) {
s_fps = s_frameCount / secondsSinceUpdate;
s_frameCount = 0;
s_fpsUpdateTime = currentTime;
}
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(s_program);
glUniform1f(loc_time, getTime());
glUniform2f(resolutionLoc, 1280.0f, 720.0f);
glBindVertexArray(s_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
char fpsText[32];
snprintf(fpsText, sizeof(fpsText), "%.3f", s_fps);
drawText(fpsText, -0.95f, 0.90f, 0.02f, 1.0f, 0.0f, 0.0f);
}
void frExit() {
cleanupTextRenderer();
glDeleteBuffers(1, &s_vbo);
glDeleteVertexArrays(1, &s_vao);
glDeleteProgram(s_program);
}
int frMain(int argc, char *argv[]) {
setMesaConfig();
if (!initEgl(nwindowGetDefault()))
return EXIT_FAILURE;
gladLoadGLLoader((GLADloadproc)eglGetProcAddress);
frSceneInit();
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
PadState pad;
padInitializeDefault(&pad);
while (appletMainLoop()) {
padUpdate(&pad);
u32 kDown = padGetButtonsDown(&pad);
if (kDown & HidNpadButton_B) {
frExit();
deinitEgl();
state = STATE_MENU;
return 0;
}
frRender();
eglSwapBuffers(s_display, s_surface);
}
frExit();
deinitEgl();
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,4 @@
void frSceneInit();
void frRender();
void frExit();
void getTime();

View File

@@ -0,0 +1,239 @@
#include <atomic>
#include <switch.h>
#include <thread>
#include "run_furmark.h"
#include "sates.h"
#include <EGL/egl.h>
#include <glad/glad.h>
extern void frSceneInit();
extern void frRender();
extern void frExit();
extern void frRamSceneInit();
extern void frRamRender();
extern void frRamExit();
extern void GPUPTSceneInit();
extern void GPUPTRender();
extern void GPUPTExit();
extern void BHRTSceneInit();
extern void BHRTRender();
extern void BHRTExit();
extern void CPURTSceneinit();
extern void CPURTRender();
extern void CPURTExit();
extern void CPURBSceneinit();
extern void CPURBRender();
extern void CPURBExit();
AppState state = STATE_MENU;
namespace {
std::thread g_thread;
std::atomic<bool> g_stop{ false };
std::atomic<bool> g_running{ false };
EGLDisplay s_dpy = EGL_NO_DISPLAY;
EGLContext s_ctx = EGL_NO_CONTEXT;
EGLSurface s_surf = EGL_NO_SURFACE;
bool eglUp() {
s_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (s_dpy == EGL_NO_DISPLAY)
return false;
if (!eglInitialize(s_dpy, nullptr, nullptr))
return false;
if (!eglBindAPI(EGL_OPENGL_API))
return false;
const EGLint cfgAttr[] = { EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_STENCIL_SIZE,
8,
EGL_NONE };
EGLConfig cfg;
EGLint n = 0;
if (!eglChooseConfig(s_dpy, cfgAttr, &cfg, 1, &n) || n == 0)
return false;
const EGLint ctxAttr[] = { EGL_CONTEXT_MAJOR_VERSION, 4, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE };
s_ctx = eglCreateContext(s_dpy, cfg, EGL_NO_CONTEXT, ctxAttr);
if (s_ctx == EGL_NO_CONTEXT)
return false;
s_surf = EGL_NO_SURFACE;
if (eglMakeCurrent(s_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, s_ctx) != EGL_TRUE)
return false;
return true;
}
void eglDown() {
if (s_dpy) {
eglMakeCurrent(s_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (s_ctx)
eglDestroyContext(s_dpy, s_ctx);
if (s_surf != EGL_NO_SURFACE)
eglDestroySurface(s_dpy, s_surf);
eglTerminate(s_dpy);
}
s_ctx = EGL_NO_CONTEXT;
s_surf = EGL_NO_SURFACE;
s_dpy = EGL_NO_DISPLAY;
eglReleaseThread();
}
void sceneInit(int which) {
switch (which) {
case 0:
frSceneInit();
break;
case 1:
frRamSceneInit();
break;
case 2:
GPUPTSceneInit();
break;
case 3:
BHRTSceneInit();
break;
case 4:
CPURTSceneinit();
break;
case 5:
CPURBSceneinit();
break;
}
}
void sceneRender(int which) {
switch (which) {
case 0:
frRender();
break;
case 1:
frRamRender();
break;
case 2:
GPUPTRender();
break;
case 3:
BHRTRender();
break;
case 4:
CPURTRender();
break;
case 5:
CPURBRender();
break;
}
}
void sceneExit(int which) {
switch (which) {
case 0:
frExit();
break;
case 1:
frRamExit();
break;
case 2:
GPUPTExit();
break;
case 3:
BHRTExit();
break;
case 4:
CPURTExit();
break;
case 5:
CPURBExit();
break;
}
}
void worker(int which) {
if (!eglUp()) {
eglDown();
g_running.store(false);
return;
}
gladLoadGLLoader((GLADloadproc)eglGetProcAddress);
GLuint fbo = 0, rbColor = 0, rbDepth = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glGenRenderbuffers(1, &rbColor);
glBindRenderbuffer(GL_RENDERBUFFER, rbColor);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1280, 720);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbColor);
glGenRenderbuffers(1, &rbDepth);
glBindRenderbuffer(GL_RENDERBUFFER, rbDepth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1280, 720);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbDepth);
glViewport(0, 0, 1280, 720);
sceneInit(which);
const u64 frameNs = 16666666ULL;
while (!g_stop.load()) {
u64 t0 = armGetSystemTick();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, 1280, 720);
sceneRender(which);
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
while (!g_stop.load()) {
GLenum r = glClientWaitSync(fence, 0, 0);
if (r != GL_TIMEOUT_EXPIRED)
break;
svcSleepThread(500000ULL);
}
glDeleteSync(fence);
u64 dt = armTicksToNs(armGetSystemTick() - t0);
if (dt < frameNs)
svcSleepThread(frameNs - dt);
}
sceneExit(which);
glDeleteFramebuffers(1, &fbo);
glDeleteRenderbuffers(1, &rbColor);
glDeleteRenderbuffers(1, &rbDepth);
eglDown();
g_running.store(false);
}
} // namespace
extern "C" void run_furmark_start(int which) {
if (g_running.load())
return;
if (g_thread.joinable())
g_thread.join();
g_stop.store(false);
g_running.store(true);
appletSetAutoSleepDisabled(true);
g_thread = std::thread(worker, which);
}
extern "C" void run_furmark_stop(void) {
g_stop.store(true);
if (g_thread.joinable())
g_thread.join();
g_running.store(false);
appletSetAutoSleepDisabled(false);
}
extern "C" int run_furmark_running(void) {
return g_running.load() ? 1 : 0;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void run_furmark_start(int which);
void run_furmark_stop(void);
int run_furmark_running(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,3 @@
enum AppState { STATE_MENU, STATE_FURMARK, STATE_FURMARK_RB, STATE_GPU_PT, STATE_BH_RT, STATE_CPU_RT, STATE_CPU_RB };
extern AppState state;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,314 @@
#pragma once
#include <arm_neon.h>
struct vec2f {
float x, y;
inline vec2f() {
}
inline vec2f(float x_, float y_) : x(x_), y(y_) {
}
};
struct vec3x4 {
float32x4_t x, y, z;
};
inline float32x4_t dot(const vec3x4 &a, const vec3x4 &b) {
float32x4_t acc = vmulq_f32(a.x, b.x);
acc = vfmaq_f32(acc, a.y, b.y);
acc = vfmaq_f32(acc, a.z, b.z);
return acc;
}
inline vec3x4 normalize(vec3x4 v) {
float32x4_t len2 = vfmaq_f32(vfmaq_f32(vmulq_f32(v.x, v.x), v.y, v.y), v.z, v.z);
float32x4_t invLen = vrsqrteq_f32(len2);
invLen = vmulq_f32(vrsqrtsq_f32(vmulq_f32(len2, invLen), invLen), invLen);
return { vmulq_f32(v.x, invLen), vmulq_f32(v.y, invLen), vmulq_f32(v.z, invLen) };
}
inline vec3x4 reflect(const vec3x4 &v, const vec3x4 &n) {
float32x4_t two_d = vmulq_n_f32(dot(v, n), 2.0f);
vec3x4 r;
r.x = vfmsq_f32(v.x, n.x, two_d);
r.y = vfmsq_f32(v.y, n.y, two_d);
r.z = vfmsq_f32(v.z, n.z, two_d);
return r;
}
struct vec3f {
float x, y, z;
inline vec3f() {
}
inline vec3f(float v) : x(v), y(v), z(v) {
}
inline vec3f(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {
}
};
inline vec3f operator+(const vec3f &a, const vec3f &b) {
return vec3f(a.x + b.x, a.y + b.y, a.z + b.z);
}
inline vec3f operator-(const vec3f &a, const vec3f &b) {
return vec3f(a.x - b.x, a.y - b.y, a.z - b.z);
}
inline vec3f operator-(const vec3f &v) {
return vec3f(-v.x, -v.y, -v.z);
}
inline vec3f &operator*=(vec3f &a, const vec3f &b) {
a.x *= b.x;
a.y *= b.y;
a.z *= b.z;
return a;
}
inline vec3f &operator*=(vec3f &a, float b) {
a.x *= b;
a.y *= b;
a.z *= b;
return a;
}
inline vec3f operator*(const vec3f &a, float b) {
return vec3f(a.x * b, a.y * b, a.z * b);
}
inline vec3f operator*(float b, const vec3f &a) {
return a * b;
}
inline vec3f operator*(const vec3f &a, const vec3f &b) {
return vec3f(a.x * b.x, a.y * b.y, a.z * b.z);
}
inline vec3f &operator+=(vec3f &a, const vec3f &b) {
a.x += b.x;
a.y += b.y;
a.z += b.z;
return a;
}
inline vec3f operator/(const vec3f &a, float b) {
float inv = 1.0f / b;
return vec3f(a.x * inv, a.y * inv, a.z * inv);
}
inline vec3f &operator/=(vec3f &a, float b) {
float inv = 1.0f / b;
a.x *= inv;
a.y *= inv;
a.z *= inv;
return a;
}
inline float dot(const vec3f &a, const vec3f &b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline vec3f normalize(const vec3f &v) {
float len2 = dot(v, v);
float invLen = 1.0f / sqrtf(len2 + 1e-20f);
return v * invLen;
}
inline vec3f cross(const vec3f &a, const vec3f &b) {
return vec3f(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
static inline float32x4_t fastRecip(float32x4_t x) {
float32x4_t r = vrecpeq_f32(x);
r = vmulq_f32(vrecpsq_f32(x, r), r);
r = vmulq_f32(vrecpsq_f32(x, r), r);
return r;
}
static inline float32x4_t fastRsqrt(float32x4_t x) {
float32x4_t r = vrsqrteq_f32(x);
r = vmulq_f32(r, vrsqrtsq_f32(vmulq_f32(x, r), r));
r = vmulq_f32(r, vrsqrtsq_f32(vmulq_f32(x, r), r));
return r;
}
static inline vec3x4 normalizeFast(const vec3x4 &v) {
float32x4_t len2 = vfmaq_f32(vmulq_f32(v.z, v.z), v.y, v.y);
len2 = vfmaq_f32(len2, v.x, v.x);
float32x4_t invLen = fastRsqrt(len2);
vec3x4 out;
out.x = vmulq_f32(v.x, invLen);
out.y = vmulq_f32(v.y, invLen);
out.z = vmulq_f32(v.z, invLen);
return out;
}
inline uint32x4_t xorshift4(uint32x4_t &s) {
s = veorq_u32(s, vshlq_n_u32(s, 13));
s = veorq_u32(s, vshrq_n_u32(s, 17));
s = veorq_u32(s, vshlq_n_u32(s, 5));
return s;
}
inline float32x4_t toFloat01(uint32x4_t x) {
uint32x4_t m = vandq_u32(x, vdupq_n_u32(0xFFFFFF));
return vmulq_n_f32(vcvtq_f32_u32(m), 1.0f / float(0xFFFFFF));
}
inline float32x4_t sin4(float32x4_t phi) {
const float32x4_t pi = vdupq_n_f32(3.14159265f);
const float32x4_t pi_2 = vdupq_n_f32(1.57079633f);
// const float32x4_t two_pi = vdupq_n_f32(6.28318531f);
uint32x4_t neg = vcgeq_f32(phi, pi);
float32x4_t xr = vbslq_f32(neg, vsubq_f32(phi, pi), phi);
uint32x4_t fold = vcgtq_f32(xr, pi_2);
xr = vbslq_f32(fold, vsubq_f32(pi, xr), xr);
float32x4_t x2 = vmulq_f32(xr, xr);
float32x4_t r = vfmsq_f32(vdupq_n_f32(1.0f / 120.0f), x2, vdupq_n_f32(1.0f / 5040.0f));
r = vfmsq_f32(vdupq_n_f32(1.0f / 6.0f), x2, r);
r = vfmsq_f32(vdupq_n_f32(1.0f), x2, r);
r = vmulq_f32(xr, r);
uint32x4_t signBit = vshlq_n_u32(neg, 31);
return vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(r), signBit));
}
inline float32x4_t cos4(float32x4_t phi) {
float32x4_t shifted = vaddq_f32(phi, vdupq_n_f32(1.57079633f));
float32x4_t two_pi = vdupq_n_f32(6.28318531f);
uint32x4_t wrap = vcgeq_f32(shifted, two_pi);
shifted = vbslq_f32(wrap, vsubq_f32(shifted, two_pi), shifted);
return sin4(shifted);
}
inline void buildONB4(const vec3x4 &n, vec3x4 &t, vec3x4 &b) {
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t neg = vdupq_n_f32(-1.0f);
uint32x4_t pos = vcgeq_f32(n.z, vdupq_n_f32(0.0f));
float32x4_t sgn = vbslq_f32(pos, one, neg);
float32x4_t denom = vaddq_f32(sgn, n.z);
float32x4_t r = vrecpeq_f32(denom);
r = vmulq_f32(vrecpsq_f32(denom, r), r);
r = vmulq_f32(vrecpsq_f32(denom, r), r);
float32x4_t a = vnegq_f32(r);
float32x4_t bc = vmulq_f32(vmulq_f32(n.x, n.y), a);
t.x = vaddq_f32(one, vmulq_f32(sgn, vmulq_f32(vmulq_f32(n.x, n.y), a)));
t.y = vmulq_f32(sgn, bc);
t.z = vnegq_f32(vmulq_f32(sgn, n.x));
b.x = bc;
b.y = vaddq_f32(sgn, vmulq_f32(vmulq_f32(n.y, n.y), a));
b.z = vnegq_f32(n.y);
}
inline vec3x4 cosineSampleHemisphere4(const vec3x4 &n, uint32x4_t &rng) {
float32x4_t u1 = toFloat01(xorshift4(rng));
float32x4_t u2 = toFloat01(xorshift4(rng));
float32x4_t r = vsqrtq_f32(u1);
float32x4_t phi = vmulq_n_f32(u2, 6.28318531f);
float32x4_t lx = vmulq_f32(r, cos4(phi));
float32x4_t ly = vmulq_f32(r, sin4(phi));
float32x4_t lz = vsqrtq_f32(vmaxq_f32(vsubq_f32(vdupq_n_f32(1.0f), u1), vdupq_n_f32(0.0)));
vec3x4 t, b;
buildONB4(n, t, b);
vec3x4 d;
d.x = vaddq_f32(vaddq_f32(vmulq_f32(lx, t.x), vmulq_f32(ly, b.x)), vmulq_f32(lz, n.x));
d.y = vaddq_f32(vaddq_f32(vmulq_f32(lx, t.y), vmulq_f32(ly, b.y)), vmulq_f32(lz, n.y));
d.z = vaddq_f32(vaddq_f32(vmulq_f32(lx, t.z), vmulq_f32(ly, b.z)), vmulq_f32(lz, n.z));
return d;
}
inline float neon_dot3(float32x4_t a, float32x4_t b) {
float32x4_t mul = vmulq_f32(a, b);
float32x2_t lo = vget_low_f32(mul);
float32x2_t hi = vget_high_f32(mul);
float32x2_t sum = vpadd_f32(lo, lo);
return vget_lane_f32(vadd_f32(sum, hi), 0);
}
inline float32x4_t neon_cross3(float32x4_t a, float32x4_t b) {
float32x4_t a_yzx = __builtin_shufflevector(a, a, 1, 2, 0, 3);
float32x4_t a_zxy = __builtin_shufflevector(a, a, 2, 0, 1, 3);
float32x4_t b_yzx = __builtin_shufflevector(b, b, 1, 2, 0, 3);
float32x4_t b_zxy = __builtin_shufflevector(b, b, 2, 0, 1, 3);
return vsubq_f32(vmulq_f32(a_yzx, b_zxy), vmulq_f32(a_zxy, b_yzx));
}
inline float32x4_t neon_normalize3(float32x4_t v) {
float32x2_t lenSq = vdup_n_f32(neon_dot3(v, v));
float32x2_t est = vrsqrte_f32(lenSq);
est = vmul_f32(est, vrsqrts_f32(vmul_f32(lenSq, est), est));
return vmulq_f32(v, vcombine_f32(est, est));
}
inline void neon_store3(float *p, float32x4_t v) {
vst1q_lane_f32(p + 0, v, 0);
vst1q_lane_f32(p + 1, v, 1);
vst1q_lane_f32(p + 2, v, 2);
}
inline float32x4_t vpermute(float32x4_t x) {
float32x4_t v34 = vdupq_n_f32(34.0f);
float32x4_t v1 = vdupq_n_f32(1.0f);
float32x4_t v289 = vdupq_n_f32(289.0f);
float32x4_t inv289 = vdupq_n_f32(1.0f / 289.0f);
float32x4_t res = vmlaq_f32(v1, x, v34);
res = vmulq_f32(res, x);
float32x4_t quotient = vmulq_f32(res, inv289);
float32x4_t floored = vrndmq_f32(quotient);
res = vmlsq_f32(res, floored, v289);
return res;
}
inline float32x4_t vtaylorInvSqrt(float32x4_t x) {
float32x4_t c1 = vdupq_n_f32(1.79284291400159f);
float32x4_t c2 = vdupq_n_f32(0.85373472095314f);
return vmlsq_f32(c1, x, c2);
}
static inline void fastSinCos(float angle, float *s, float *c) {
const float INV_TWO_PI = 0.15915494309f;
const float TWO_PI = 6.28318530718f;
// const float PI = 3.14159265359f;
float x = angle - TWO_PI * floorf(angle * INV_TWO_PI + 0.5f);
float x2 = x * x;
float x3 = x2 * x;
float x5 = x3 * x2;
float x7 = x5 * x3;
*s = x + x3 * (-0.16666667163f) + x5 * (0.00833333842f) + x7 * (-0.00019840680f);
float x4 = x2 * x2;
float x6 = x4 * x2;
*c = 1.0f + x2 * (-0.49999997020f) + x4 * (0.04166664556f) + x6 * (-0.00138873165f);
}

View File

@@ -0,0 +1,251 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <switch.h>
#include "gpu_bw.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl31.h>
static EGLDisplay s_display = EGL_NO_DISPLAY;
static EGLContext s_context = EGL_NO_CONTEXT;
static bool egl_init(void) {
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (s_display == EGL_NO_DISPLAY) {
printf("EGL: no display\n");
consoleUpdate(NULL);
return false;
}
if (!eglInitialize(s_display, NULL, NULL)) {
printf("EGL: initialize failed (0x%x)\n", eglGetError());
consoleUpdate(NULL);
return false;
}
if (!eglBindAPI(EGL_OPENGL_API)) {
printf("EGL: bindAPI(OPENGL) failed (0x%x)\n", eglGetError());
consoleUpdate(NULL);
return false;
}
const char *exts = eglQueryString(s_display, EGL_EXTENSIONS);
if (!exts || !strstr(exts, "EGL_KHR_surfaceless_context")) {
printf("EGL: no surfaceless_context\n");
consoleUpdate(NULL);
return false;
}
static const EGLint cfg_attribs[] = {
EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_STENCIL_SIZE,
8,
EGL_NONE,
};
EGLConfig cfg;
EGLint n;
if (!eglChooseConfig(s_display, cfg_attribs, &cfg, 1, &n) || !n) {
printf("EGL: chooseConfig failed n=%d (0x%x)\n", (int)n, eglGetError());
consoleUpdate(NULL);
return false;
}
static const EGLint ctx_attribs[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_NONE,
};
s_context = eglCreateContext(s_display, cfg, EGL_NO_CONTEXT, ctx_attribs);
if (s_context == EGL_NO_CONTEXT) {
printf("EGL: createContext failed (0x%x)\n", eglGetError());
consoleUpdate(NULL);
return false;
}
if (eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, s_context) != EGL_TRUE) {
printf("EGL: makeCurrent failed (0x%x)\n", eglGetError());
consoleUpdate(NULL);
return false;
}
return true;
}
static void egl_exit(void) {
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (s_context != EGL_NO_CONTEXT) {
eglDestroyContext(s_display, s_context);
s_context = EGL_NO_CONTEXT;
}
if (s_display != EGL_NO_DISPLAY) {
eglTerminate(s_display);
s_display = EGL_NO_DISPLAY;
}
eglReleaseThread();
}
static GLuint compile_compute(const char *src, const char *name) {
GLuint sh = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(sh, 1, &src, NULL);
glCompileShader(sh);
GLint ok = 0;
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[256] = { 0 };
glGetShaderInfoLog(sh, sizeof(log), NULL, log);
printf("Compute shader compile failed: %s\n", log[0] ? log : name);
glDeleteShader(sh);
return 0;
}
GLuint prog = glCreateProgram();
glAttachShader(prog, sh);
glLinkProgram(prog);
glDeleteShader(sh);
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
glDeleteProgram(prog);
return 0;
}
return prog;
}
static double run_pass(GLuint prog, GLuint ssbo_src, GLuint ssbo_dst, size_t buf_bytes, int loops) {
GLuint groups = (GLuint)(buf_bytes >> 10);
glUseProgram(prog);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo_src);
if (ssbo_dst)
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo_dst);
glDispatchCompute(groups, 1, 1);
glFinish();
if (glGetError() != GL_NO_ERROR)
return 0.0;
uint64_t t0, t1;
asm volatile("mrs %0, cntpct_el0" : "=r"(t0));
for (int i = 0; i < loops; i++)
glDispatchCompute(groups, 1, 1);
glFinish();
asm volatile("mrs %0, cntpct_el0" : "=r"(t1));
if (glGetError() != GL_NO_ERROR)
return 0.0;
double elapsed = (double)(t1 - t0) * 625.0 / 12.0 / 1000000000.0;
if (elapsed <= 0.0)
return 0.0;
return ((double)buf_bytes * (double)loops) / elapsed / 1000000.0;
}
bool gpu_bw_run(bool is_4gb, double *copy_out, double *read_out, double *write_out) {
static const char *src_copy = "\n#version 430\n"
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
"layout(std430, binding = 1) buffer dstBuffer { volatile uint dst[]; };\n"
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
"void main() {\n"
" dst[gl_GlobalInvocationID.x] = src[gl_GlobalInvocationID.x];\n"
"}\n";
static const char *src_read = "\n#version 430\n"
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
"shared uint tmp;\n"
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
"void main() {\n"
" tmp |= src[gl_GlobalInvocationID.x];\n"
"}\n";
static const char *src_write = "\n#version 430\n"
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
"void main() {\n"
" src[gl_GlobalInvocationID.x] = 0xAAAAAAAAu;\n"
"}\n";
if (!egl_init()) {
printf("Failed to initialize EGL/GL for GPU benchmark.\n");
consoleUpdate(NULL);
return false;
}
GLint max_ssbo = 0;
glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo);
if (max_ssbo < 1) {
printf("Failed to query GPU SSBO size.\n");
consoleUpdate(NULL);
egl_exit();
return false;
}
size_t buf_bytes = (size_t)max_ssbo;
if (buf_bytes > 0x8000000)
buf_bytes = 0x8000000;
if (!is_4gb)
buf_bytes >>= 1;
buf_bytes &= ~(size_t)0x3FF;
if (buf_bytes < 0x400) {
printf("GPU benchmark buffer is too small.\n");
consoleUpdate(NULL);
egl_exit();
return false;
}
int loops = is_4gb ? 400 : 800;
int wloops = loops / 10;
GLuint prog_copy = compile_compute(src_copy, "GPU Copy");
GLuint prog_read = compile_compute(src_read, "GPU Read");
GLuint prog_write = compile_compute(src_write, "GPU Write");
if (!prog_copy || !prog_read || !prog_write) {
if (prog_copy)
glDeleteProgram(prog_copy);
if (prog_read)
glDeleteProgram(prog_read);
if (prog_write)
glDeleteProgram(prog_write);
egl_exit();
return false;
}
GLuint ssbo[2] = { 0, 0 };
glGenBuffers(2, ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[0]);
glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)buf_bytes, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[1]);
glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)buf_bytes, NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
if (glGetError() != GL_NO_ERROR) {
printf("Failed to allocate GPU buffers!\n");
consoleUpdate(NULL);
glDeleteBuffers(2, ssbo);
glDeleteProgram(prog_copy);
glDeleteProgram(prog_read);
glDeleteProgram(prog_write);
egl_exit();
return false;
}
run_pass(prog_write, ssbo[0], 0, buf_bytes, wloops);
run_pass(prog_read, ssbo[0], 0, buf_bytes, wloops);
*copy_out = run_pass(prog_copy, ssbo[0], ssbo[1], buf_bytes, loops);
*read_out = run_pass(prog_read, ssbo[0], 0, buf_bytes, loops);
*write_out = run_pass(prog_write, ssbo[0], 0, buf_bytes, loops);
glDeleteBuffers(2, ssbo);
glDeleteProgram(prog_copy);
glDeleteProgram(prog_read);
glDeleteProgram(prog_write);
egl_exit();
return true;
}

View File

@@ -0,0 +1,4 @@
#pragma once
#include <stdbool.h>
bool gpu_bw_run(bool is_4gb, double *copy_out, double *read_out, double *write_out);

View File

@@ -0,0 +1,305 @@
extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include "gpu_stress.h"
}
#include <deko3d.hpp>
#include "compute_shader_bin.h"
static constexpr uint32_t kDispatchX = 96u;
static constexpr uint32_t kLocalX = 128u;
static constexpr uint32_t kThreads = kDispatchX * kLocalX;
static constexpr uint32_t kSeedBytes = kThreads * 4u;
static constexpr uint32_t kOutU32s = kThreads * 4u;
static constexpr uint32_t kOutBytes = kOutU32s * 4u;
static constexpr uint32_t kScrBytes = 0x400000u;
static constexpr uint32_t kUboAlloc = 0x1000u;
static constexpr uint32_t kUboBindSz = 0x100u;
static constexpr uint32_t kCmdbufSz = 0x20000u;
static constexpr uint32_t kDataFlags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
static constexpr uint32_t kCodeFlags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code;
static constexpr double kOpsPerIter = 259.0;
static constexpr uint32_t kTargetMs = 15u;
static constexpr uint32_t kWindowIters = 16u;
static constexpr bool kCpuDetect = true;
static constexpr bool kCvDetect = true;
static uint64_t read_cntpct(void) {
uint64_t t;
asm volatile("mrs %0, cntpct_el0" : "=r"(t));
return t;
}
static uint64_t cntpct_to_ms(uint64_t ticks) {
return (ticks * 625ULL) / 12000000ULL;
}
static uint32_t round_up_4k(uint32_t v) {
return (v + 4095u) & ~4095u;
}
static void init_seed_block(uint32_t *out, uint32_t count) {
for (uint32_t a = 0; a < count; a++) {
uint32_t m = a * 0x9e3779b9u;
uint8_t b0 = (uint8_t)(m >> 16) ^ (uint8_t)(a) ^ 0xa5u;
uint8_t b1 = (uint8_t)(m >> 24) ^ (uint8_t)(a >> 8) ^ 0xa5u;
uint32_t v = ((uint32_t)b1 << 24) | ((uint32_t)b0 << 16) | ((uint32_t)((uint8_t)(b1 ^ (uint8_t)(m >> 8) ^ 0x5au)) << 8) |
(uint32_t)((uint8_t)(b0 ^ (uint8_t)m ^ 0x5au));
uint32_t u = v * 0x7feb352du;
uint8_t c1 = (uint8_t)(u >> 24);
uint32_t w = ((uint32_t)c1 << 24) | ((uint32_t)((uint8_t)((c1 >> 7) ^ (uint8_t)(u >> 16))) << 16) |
((uint32_t)((uint8_t)((uint8_t)(u >> 23) ^ (uint8_t)(u >> 8))) << 8) | (uint32_t)((uint8_t)((uint8_t)(u >> 15) ^ (uint8_t)u));
out[a] = (w * 0x846ca68bu) | 1u;
}
}
static void init_scratch(uint32_t *out, uint32_t count) {
uint32_t s1 = 0x6d2b79f5u;
uint32_t s2 = 0xc2b2ae35u;
uint32_t acc = 0u;
for (uint32_t i = 0; i < count; i++) {
s1 += acc;
uint32_t os2 = s2;
acc += 0x9e3779b9u;
s2 = os2 + 0x85ebca6bu;
uint32_t m1 = os2;
m1 = (m1 ^ (m1 >> 16u)) * 0x7feb352du;
m1 = (m1 ^ (m1 >> 15u)) * 0x846ca68bu;
m1 ^= m1 >> 16u;
uint32_t m2 = s1;
m2 = (m2 ^ (m2 >> 16u)) * 0x7feb352du;
m2 = (m2 ^ (m2 >> 15u)) * 0x846ca68bu;
m2 ^= m2 >> 16u;
out[i] = m1 ^ m2;
}
}
struct StressState {
dk::UniqueDevice device;
dk::UniqueQueue queue;
dk::UniqueMemBlock codeBlock;
dk::UniqueMemBlock paramsBlock;
dk::UniqueMemBlock seedBlock;
dk::UniqueMemBlock scratchBlock;
dk::UniqueMemBlock outABlock;
dk::UniqueMemBlock outBBlock;
dk::UniqueMemBlock cmdmemBlock;
dk::UniqueCmdBuf cmdbuf;
dk::Shader computeShader;
DkCmdList listA = 0;
DkCmdList listB = 0;
uint32_t *params = nullptr;
uint32_t *outA = nullptr;
uint32_t *outB = nullptr;
uint32_t *golden = nullptr;
uint32_t batch_size = 1024u;
uint64_t cum_dispatches = 0;
uint64_t start_ms = 0;
bool initialized = false;
bool failed = false;
};
static StressState g;
static bool stress_init(void) {
g.device = dk::DeviceMaker{}.create();
if (!g.device) {
printf("deko3d: device create failed\n");
return false;
}
g.queue = dk::QueueMaker{ g.device }.setFlags(DkQueueFlags_Compute).create();
if (!g.queue) {
printf("deko3d: queue create failed\n");
return false;
}
uint32_t codeSize = round_up_4k(compute_shader_bin_size);
g.codeBlock = dk::MemBlockMaker{ g.device, codeSize }.setFlags(kCodeFlags).create();
if (!g.codeBlock) {
printf("deko3d: code memblock failed\n");
return false;
}
memcpy(g.codeBlock.getCpuAddr(), compute_shader_bin, compute_shader_bin_size);
dk::ShaderMaker{ g.codeBlock, 0 }.initialize(g.computeShader);
g.paramsBlock = dk::MemBlockMaker{ g.device, kUboAlloc }.setFlags(kDataFlags).create();
g.seedBlock = dk::MemBlockMaker{ g.device, kSeedBytes }.setFlags(kDataFlags).create();
g.scratchBlock = dk::MemBlockMaker{ g.device, kScrBytes }.setFlags(kDataFlags).create();
g.outABlock = dk::MemBlockMaker{ g.device, kOutBytes }.setFlags(kDataFlags).create();
g.outBBlock = dk::MemBlockMaker{ g.device, kOutBytes }.setFlags(kDataFlags).create();
g.cmdmemBlock = dk::MemBlockMaker{ g.device, kCmdbufSz }.setFlags(kDataFlags).create();
if (!g.paramsBlock || !g.seedBlock || !g.scratchBlock || !g.outABlock || !g.outBBlock || !g.cmdmemBlock) {
printf("deko3d: memblock alloc failed\n");
return false;
}
g.params = (uint32_t *)g.paramsBlock.getCpuAddr();
auto *seeds = (uint32_t *)g.seedBlock.getCpuAddr();
auto *scratch = (uint32_t *)g.scratchBlock.getCpuAddr();
g.outA = (uint32_t *)g.outABlock.getCpuAddr();
g.outB = (uint32_t *)g.outBBlock.getCpuAddr();
g.batch_size = 1024u;
g.params[0] = g.batch_size;
g.params[1] = 0;
g.params[2] = 0;
g.params[3] = 0;
init_seed_block(seeds, kThreads);
init_scratch(scratch, kScrBytes / 4u);
for (uint32_t i = 0; i < kOutU32s; i++) {
g.outA[i] = 0xcafebabeu;
g.outB[i] = 0xcafebabeu;
}
g.cmdbuf = dk::CmdBufMaker{ g.device }.create();
if (!g.cmdbuf) {
printf("deko3d: cmdbuf create failed\n");
return false;
}
auto record = [&](dk::MemBlock outputBlock, uint32_t memOffset) -> DkCmdList {
g.cmdbuf.addMemory(g.cmdmemBlock, memOffset, kCmdbufSz / 2);
g.cmdbuf.bindShaders(DkStageFlag_Compute, { &g.computeShader });
DkBufExtents ubo = { g.paramsBlock.getGpuAddr(), kUboBindSz };
g.cmdbuf.bindUniformBuffers(DkStage_Compute, 0, { ubo });
DkBufExtents sb0 = { g.seedBlock.getGpuAddr(), kSeedBytes };
DkBufExtents sb1 = { outputBlock.getGpuAddr(), kOutBytes };
DkBufExtents sb2 = { g.scratchBlock.getGpuAddr(), kScrBytes };
g.cmdbuf.bindStorageBuffers(DkStage_Compute, 0, { sb0, sb1, sb2 });
g.cmdbuf.dispatchCompute(kDispatchX, 1, 1);
return g.cmdbuf.finishList();
};
g.listA = record(g.outABlock, 0);
g.listB = record(g.outBBlock, kCmdbufSz / 2);
uint64_t t0 = read_cntpct();
g.queue.submitCommands(g.listA);
g.queue.waitIdle();
uint64_t cal_ms = cntpct_to_ms(read_cntpct() - t0);
if (cal_ms == 0) {
g.batch_size = 1024u;
} else {
double scaled = ((double)kTargetMs / (double)cal_ms) * 1024.0;
if (scaled < 256.0)
g.batch_size = 256u;
else if (scaled > 65536.0)
g.batch_size = 65536u;
else
g.batch_size = (uint32_t)scaled;
}
g.params[0] = g.batch_size;
for (uint32_t i = 0; i < kOutU32s; i++)
g.outA[i] = 0xcafebabeu;
g.queue.submitCommands(g.listA);
g.queue.waitIdle();
bool gpu_wrote = false;
for (uint32_t i = 0; i < kOutU32s && !gpu_wrote; i++)
if (g.outA[i] != 0xcafebabeu)
gpu_wrote = true;
if (!gpu_wrote) {
printf("deko3d: golden dispatch produced no GPU writes\n");
return false;
}
g.golden = (uint32_t *)malloc(kOutBytes);
if (!g.golden) {
printf("deko3d: OOM for golden\n");
return false;
}
memcpy(g.golden, g.outA, kOutBytes);
g.start_ms = cntpct_to_ms(read_cntpct());
g.cum_dispatches = 0;
return true;
}
extern "C" bool gpu_stress_run(double *gflops_out, uint64_t *dispatches_out, uint64_t *mismatches_out) {
*gflops_out = 0.0;
*dispatches_out = 0;
*mismatches_out = 0;
if (g.failed)
return false;
if (!g.initialized) {
if (!stress_init()) {
g.failed = true;
return false;
}
g.initialized = true;
}
uint64_t window_dispatches = 0;
uint64_t window_mismatches = 0;
for (uint32_t n = 0; n < kWindowIters; n++) {
g.queue.submitCommands(g.listA);
window_dispatches++;
if (kCvDetect) {
g.queue.submitCommands(g.listB);
window_dispatches++;
}
g.queue.waitIdle();
if (kCpuDetect) {
if (memcmp(g.outA, g.golden, kOutBytes) != 0)
for (uint32_t i = 0; i < kOutU32s; i++)
if (g.outA[i] != g.golden[i])
window_mismatches++;
}
if (kCvDetect) {
if (memcmp(g.outA, g.outB, kOutBytes) != 0)
for (uint32_t i = 0; i < kOutU32s; i++)
if (g.outB[i] != g.outA[i])
window_mismatches++;
}
}
g.cum_dispatches += window_dispatches;
uint64_t now_ms = cntpct_to_ms(read_cntpct());
double elapsed_s = (double)(now_ms - g.start_ms) / 1000.0;
double gflops = (elapsed_s > 0.0) ? ((double)g.batch_size * kOpsPerIter * (double)kThreads * (double)g.cum_dispatches) / (elapsed_s * 1e9) : 0.0;
*gflops_out = gflops;
*dispatches_out = window_dispatches;
*mismatches_out = window_mismatches;
return true;
}
extern "C" void gpu_stress_shutdown(void) {
if (g.golden) {
free(g.golden);
g.golden = nullptr;
}
if (g.queue)
g.queue.waitIdle();
g.cmdbuf = {};
g.cmdmemBlock = {};
g.outBBlock = {};
g.outABlock = {};
g.scratchBlock = {};
g.seedBlock = {};
g.paramsBlock = {};
g.codeBlock = {};
g.queue = {};
g.device = {};
g.listA = g.listB = 0;
g.params = g.outA = g.outB = nullptr;
g.batch_size = 1024u;
g.cum_dispatches = 0;
g.start_ms = 0;
g.initialized = false;
g.failed = false;
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
bool gpu_stress_run(double *gflops_out, uint64_t *dispatches_out, uint64_t *mismatches_out);
void gpu_stress_shutdown(void);

View File

@@ -0,0 +1,35 @@
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include <assert.h>
#include <switch.h>
#include "hoc_clk.h"
#include <hocclk/clock_manager.h>
#define HOCCLK_SERVICE "hoc:clk"
#define HOCCLK_CMD_GET_CURRENT_CONTEXT 2
static Service g_srv;
static bool g_active = false;
bool hocclk_init(void) {
if (g_active)
return true;
Result rc = smGetService(&g_srv, HOCCLK_SERVICE);
g_active = R_SUCCEEDED(rc);
return g_active;
}
void hocclk_exit(void) {
if (g_active) {
serviceClose(&g_srv);
g_active = false;
}
}
bool hocclk_get(HocClkContext *out) {
if (!g_active)
return false;
Result rc = serviceDispatch(&g_srv, HOCCLK_CMD_GET_CURRENT_CONTEXT, .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
.buffers = { { out, sizeof(*out) } }, );
return R_SUCCEEDED(rc);
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <stdbool.h>
#include <hocclk/clock_manager.h>
#ifdef __cplusplus
extern "C" {
#endif
bool hocclk_init(void);
void hocclk_exit(void);
bool hocclk_get(HocClkContext *out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,585 @@
#include <atomic>
#include <borealis.hpp>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
extern "C" {
#include <switch.h>
#include <unistd.h>
#include "bench.h"
#include "gpu_bw.h"
#include "gpu_stress.h"
#include "hoc_clk.h"
#include "run_furmark.h"
}
static std::string fstr(const char *f, double v) {
char b[64];
std::snprintf(b, sizeof(b), f, v);
return b;
}
static std::string fstru(const char *f, unsigned long long v) {
char b[64];
std::snprintf(b, sizeof(b), f, v);
return b;
}
static brls::Label *makeRow(brls::Box *parent, const std::string &name) {
auto *row = new brls::Box(brls::Axis::ROW);
row->setMarginBottom(8.0f);
auto *n = new brls::Label();
n->setText(name);
n->setGrow(1.0f);
auto *v = new brls::Label();
v->setText("-");
row->addView(n);
row->addView(v);
parent->addView(row);
return v;
}
struct StatCells {
brls::Label *load = nullptr, *clock = nullptr, *volt = nullptr, *temp = nullptr;
};
static brls::Label *statCell(brls::Box *row, float fs) {
auto *l = new brls::Label();
if (fs > 0.0f)
l->setFontSize(fs);
row->addView(l);
return l;
}
static void statSep(brls::Box *row, float fs) {
auto *s = new brls::Label();
s->setText("|");
if (fs > 0.0f)
s->setFontSize(fs);
s->setTextColor(nvgRGB(120, 120, 120));
s->setMarginLeft(7.0f);
s->setMarginRight(7.0f);
row->addView(s);
}
static void fmtLoad(brls::Label *l, unsigned val, bool isRam) {
char b[32];
if (isRam)
std::snprintf(b, sizeof b, "%u.%u GB/s", val / 1000u, (val % 1000u) / 100u);
else
std::snprintf(b, sizeof b, "%u%%", val);
l->setText(b);
}
static void fmtClock1(brls::Label *l, uint32_t hz) {
char b[32];
std::snprintf(b, sizeof b, "%u.%u MHz", hz / 1000000u, (hz / 100000u) % 10u);
l->setText(b);
}
static void fmtClock0(brls::Label *l, uint32_t hz) {
char b[32];
std::snprintf(b, sizeof b, "%u MHz", hz / 1000000u);
l->setText(b);
}
static void fmtVolt(brls::Label *l, uint32_t uv) {
char b[32];
std::snprintf(b, sizeof b, "%u mV", uv / 1000u);
l->setText(b);
}
static void fmtTemp(brls::Label *l, int32_t mc) {
char b[32];
std::snprintf(b, sizeof b, "%d°C", mc / 1000);
l->setText(b);
}
class SysInfoTab : public brls::Box {
public:
SysInfoTab() {
this->setAxis(brls::Axis::COLUMN);
this->setGrow(1.0f);
this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);
auto *clk = new brls::Header();
clk->setTitle("Clocks");
this->addView(clk);
cpuR = makeCompRow(this, "CPU");
gpuR = makeCompRow(this, "GPU");
ramR = makeCompRow(this, "RAM");
auto *sys = new brls::Header();
sys->setTitle("System");
this->addView(sys);
mode = makeRow(this, "Mode");
threads = makeRow(this, "Threads");
hocclk_init();
refresh();
}
void frame(brls::FrameContext *ctx) override {
if (++tick >= 15) {
tick = 0;
refresh();
}
brls::Box::frame(ctx);
}
private:
static StatCells makeCompRow(brls::Box *parent, const char *name) {
auto *row = new brls::Box(brls::Axis::ROW);
row->setMarginBottom(10.0f);
auto *n = new brls::Label();
n->setText(name);
n->setGrow(1.0f);
row->addView(n);
StatCells c;
c.load = statCell(row, 0);
statSep(row, 0);
c.clock = statCell(row, 0);
statSep(row, 0);
c.volt = statCell(row, 0);
statSep(row, 0);
c.temp = statCell(row, 0);
parent->addView(row);
return c;
}
void setRow(StatCells &r, unsigned loadOrBw, bool isRam, uint32_t hz, uint32_t uv, int32_t mc) {
fmtLoad(r.load, loadOrBw, isRam);
fmtClock1(r.clock, hz);
fmtVolt(r.volt, uv);
fmtTemp(r.temp, mc);
}
void naRow(StatCells &r) {
r.load->setText("N/A");
r.clock->setText("-");
r.volt->setText("-");
r.temp->setText("-");
}
void refresh() {
sysinfo_t s;
bench_get_sysinfo(&s);
mode->setText(s.is_4gb ? "Application" : "Applet");
threads->setText(fstru("%llu", (unsigned long long)s.threads));
HocClkContext c;
if (hocclk_get(&c)) {
setRow(cpuR, c.stable.partLoad[3] / 10, false, c.stable.freqs[0], c.stable.voltages[2], c.stable.temps[5]);
setRow(gpuR, c.stable.partLoad[2] / 10, false, c.stable.freqs[1], c.stable.voltages[3], c.stable.temps[6]);
setRow(ramR, c.stable.partLoad[6], true, c.stable.freqs[2], c.stable.voltages[1], c.stable.temps[7]);
} else {
naRow(cpuR);
naRow(gpuR);
naRow(ramR);
}
}
StatCells cpuR, gpuR, ramR;
brls::Label *mode, *threads;
int tick = 0;
};
class BenchTab : public brls::Box {
public:
BenchTab() {
this->setAxis(brls::Axis::COLUMN);
this->setGrow(1.0f);
this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);
runBtn = new brls::Button();
runBtn->setText("Run");
runBtn->registerClickAction([this](brls::View *) {
start();
return true;
});
this->addView(runBtn);
status = new brls::Label();
status->setText("Idle");
status->setMarginTop(8.0f);
status->setMarginBottom(6.0f);
this->addView(status);
bar = new brls::Box(brls::Axis::ROW);
bar->setHeight(18.0f);
bar->setWidthPercentage(100.0f);
bar->setMarginBottom(14.0f);
barFill = new brls::Rectangle();
barFill->setColor(nvgRGB(0, 193, 210));
barFill->setWidthPercentage(0.0f);
barTrack = new brls::Rectangle();
barTrack->setColor(nvgRGB(48, 48, 54));
barTrack->setGrow(1.0f);
bar->addView(barFill);
bar->addView(barTrack);
this->addView(bar);
auto *h1 = new brls::Header();
h1->setTitle("GPU bandwidth");
this->addView(h1);
gpuCopy = makeRow(this, "GPU Copy");
gpuRead = makeRow(this, "GPU Read");
gpuWrite = makeRow(this, "GPU Write");
auto *h2 = new brls::Header();
h2->setTitle("CPU bandwidth");
this->addView(h2);
cpuCopy = makeRow(this, "CPU Copy");
cpuRead = makeRow(this, "CPU Read");
cpuWrite = makeRow(this, "CPU Write");
auto *h3 = new brls::Header();
h3->setTitle("RAM latency");
this->addView(h3);
l2 = makeRow(this, "L2");
ram = makeRow(this, "Full RAM");
}
~BenchTab() override {
if (ctx)
bench_end(ctx);
}
void frame(brls::FrameContext *fc) override {
if (running) {
if (primed) {
primed = false;
} else {
const char *label = "";
float frac = 0.0f;
bool more = bench_step(ctx, &res, &label, &frac);
setProgress(frac);
if (more) {
status->setText(fstr("%.0f%%", frac * 100.0f) + " " + label);
primed = true;
} else {
showResults();
bench_end(ctx);
ctx = nullptr;
running = false;
status->setText("Done");
appletSetAutoSleepDisabled(false);
}
}
}
brls::Box::frame(fc);
}
private:
void setProgress(float f) {
barFill->setWidthPercentage(f * 100.0f);
}
void showResults() {
gpuCopy->setText(fstr("%.1f MB/s", res.gpu_copy));
gpuRead->setText(fstr("%.1f MB/s", res.gpu_read));
gpuWrite->setText(fstr("%.1f MB/s", res.gpu_write));
cpuCopy->setText(fstr("%.1f MB/s", res.cpu_copy));
cpuRead->setText(fstr("%.1f MB/s", res.cpu_read));
cpuWrite->setText(fstr("%.1f MB/s", res.cpu_write));
l2->setText(fstr("%.1f ns", res.l2_ns));
ram->setText(fstr("%.1f ns", res.ram_ns));
}
void start() {
if (running)
return;
memset(&res, 0, sizeof(res));
ctx = bench_begin();
if (!ctx) {
status->setText("Out of memory");
return;
}
running = true;
primed = true;
setProgress(0.0f);
status->setText("Running benchmark...");
appletSetAutoSleepDisabled(true);
}
bench_ctx *ctx = nullptr;
bool running = false;
bool primed = false;
bench_results_t res{};
brls::Button *runBtn;
brls::Label *status;
brls::Box *bar;
brls::Rectangle *barFill, *barTrack;
brls::Label *gpuCopy, *gpuRead, *gpuWrite, *cpuCopy, *cpuRead, *cpuWrite, *l2, *ram;
};
struct StressShared {
std::atomic<bool> running{ false }, stop{ false };
std::atomic<double> gflops{ 0.0 };
std::atomic<uint64_t> dispatches{ 0 }, mismatches{ 0 };
std::thread worker;
};
class StressTab : public brls::Box {
public:
StressTab() {
this->setAxis(brls::Axis::COLUMN);
this->setGrow(1.0f);
this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);
toggle = new brls::Button();
toggle->setText("Start GPU stress");
toggle->registerClickAction([this](brls::View *) {
onToggle();
return true;
});
this->addView(toggle);
statusL = new brls::Label();
statusL->setText("Stopped");
statusL->setMarginTop(8.0f);
statusL->setMarginBottom(8.0f);
this->addView(statusL);
auto *h = new brls::Header();
h->setTitle("Live");
this->addView(h);
gflops = makeRow(this, "GFLOPS");
dispatches = makeRow(this, "Dispatches");
mismatches = makeRow(this, "Mismatches");
auto *info = new brls::Label();
info->setText("GPU stress test, Mismatches > 0 indicate instability.");
info->setMarginTop(16.0f);
this->addView(info);
}
~StressTab() override {
stopWorker();
}
void willDisappear(bool resetState = false) override {
stopWorker();
brls::Box::willDisappear(resetState);
}
void frame(brls::FrameContext *ctx) override {
if (sh.running.load()) {
gflops->setText(fstr("%.1f", sh.gflops.load()));
dispatches->setText(fstru("%llu", (unsigned long long)sh.dispatches.load()));
mismatches->setText(fstru("%llu", (unsigned long long)sh.mismatches.load()));
}
brls::Box::frame(ctx);
}
private:
void onToggle() {
if (sh.running.load())
stopWorker();
else
startWorker();
}
void startWorker() {
if (sh.worker.joinable())
sh.worker.join();
sh.stop.store(false);
sh.running.store(true);
toggle->setText("Stop GPU stress");
statusL->setText("Running...");
sh.worker = std::thread([this] {
appletSetAutoSleepDisabled(true);
uint64_t totD = 0, totM = 0;
while (!sh.stop.load()) {
double g = 0;
uint64_t d = 0, m = 0;
if (!gpu_stress_run(&g, &d, &m))
break;
totD += d;
totM += m;
sh.gflops.store(g);
sh.dispatches.store(totD);
sh.mismatches.store(totM);
}
appletSetAutoSleepDisabled(false);
sh.running.store(false);
});
}
void stopWorker() {
sh.stop.store(true);
if (sh.worker.joinable())
sh.worker.join();
gpu_stress_shutdown();
sh.running.store(false);
if (toggle)
toggle->setText("Start GPU stress");
if (statusL)
statusL->setText("Stopped");
}
StressShared sh;
brls::Button *toggle;
brls::Label *statusL, *gflops, *dispatches, *mismatches;
};
class FurmarkTab : public brls::Box {
public:
FurmarkTab(int which, const char *desc) : which(which) {
this->setAxis(brls::Axis::COLUMN);
this->setGrow(1.0f);
this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);
toggle = new brls::Button();
toggle->setText("Start");
toggle->registerClickAction([this](brls::View *) {
onToggle();
return true;
});
this->addView(toggle);
statusL = new brls::Label();
statusL->setText("Stopped");
statusL->setMarginTop(8.0f);
statusL->setMarginBottom(12.0f);
this->addView(statusL);
auto *info = new brls::Label();
info->setText(desc);
this->addView(info);
}
~FurmarkTab() override {
if (run_furmark_running())
run_furmark_stop();
}
void willDisappear(bool resetState = false) override {
if (run_furmark_running())
run_furmark_stop();
brls::Box::willDisappear(resetState);
}
void frame(brls::FrameContext *fc) override {
bool r = run_furmark_running() != 0;
if (r != shown) {
shown = r;
toggle->setText(r ? "Stop" : "Start");
statusL->setText(r ? "Running..." : "Stopped");
}
brls::Box::frame(fc);
}
private:
void onToggle() {
if (run_furmark_running())
run_furmark_stop();
else
run_furmark_start(which);
}
int which;
bool shown = false;
brls::Button *toggle;
brls::Label *statusL;
};
class AppFrame : public brls::TabFrame {
public:
AppFrame() {
box = dynamic_cast<brls::Box *>(this->getView("brls/applet_frame/header_stats"));
if (box) {
box->setJustifyContent(brls::JustifyContent::FLEX_END);
box->setAlignItems(brls::AlignItems::CENTER);
const float fs = 13.0f;
for (int i = 0; i < 3; i++) {
grp[i].load = statCell(box, fs);
if (i)
grp[i].load->setMarginLeft(18.0f);
statSep(box, fs);
grp[i].clock = statCell(box, fs);
statSep(box, fs);
grp[i].temp = statCell(box, fs);
}
}
hocclk_init();
update();
}
void frame(brls::FrameContext *ctx) override {
if (box && ++tick >= 12) {
tick = 0;
update();
}
brls::TabFrame::frame(ctx);
}
private:
void setGrp(StatCells &g, const char *name, unsigned loadOrBw, bool isRam, uint32_t hz, int32_t mc) {
char b[48];
if (isRam)
std::snprintf(b, sizeof b, "%s %u.%u GB/s", name, loadOrBw / 1000u, (loadOrBw % 1000u) / 100u);
else
std::snprintf(b, sizeof b, "%s %u%%", name, loadOrBw);
g.load->setText(b);
fmtClock0(g.clock, hz);
fmtTemp(g.temp, mc);
}
void update() {
if (!box)
return;
HocClkContext c;
if (!hocclk_get(&c)) {
grp[0].load->setText("hoc:clk N/A");
grp[0].clock->setText("-");
grp[0].temp->setText("-");
grp[1].load->setText("-");
grp[1].clock->setText("-");
grp[1].temp->setText("-");
grp[2].load->setText("-");
grp[2].clock->setText("-");
grp[2].temp->setText("-");
return;
}
setGrp(grp[0], "CPU", c.stable.partLoad[3] / 10, false, c.stable.freqs[0], c.stable.temps[5]);
setGrp(grp[1], "GPU", c.stable.partLoad[2] / 10, false, c.stable.freqs[1], c.stable.temps[6]);
setGrp(grp[2], "RAM", c.stable.partLoad[6], true, c.stable.freqs[2], c.stable.temps[7]);
}
brls::Box *box = nullptr;
StatCells grp[3];
int tick = 0;
};
class MainActivity : public brls::Activity {
public:
brls::View *createContentView() override {
auto *tab = new AppFrame();
tab->setTitle("Benchmark Toolbox");
tab->setIconFromRes("img/logo.png");
tab->addTab("System Info", [] { return new SysInfoTab(); });
tab->addSeparator();
tab->addTab("Membench", [] { return new BenchTab(); });
tab->addTab("GPU Test", [] { return new StressTab(); });
tab->addSeparator();
tab->addTab("Furmark", [] { return new FurmarkTab(0, "FurMark for Switch (48 step)"); });
tab->addTab("Furmark RAM", [] { return new FurmarkTab(1, "FurMark with extra ram stress"); });
tab->addTab("GPU Path Trace", [] { return new FurmarkTab(2, "GPU Path Tracer"); });
tab->addTab("Black Hole", [] { return new FurmarkTab(3, "CPU+GPU black-hole."); });
tab->addTab("CPU Ray Trace", [] { return new FurmarkTab(4, "CPU Path Tracer"); });
tab->addTab("CPU RAM", [] { return new FurmarkTab(5, "CPU RT with extra RAM stress"); });
return tab;
}
};
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
brls::Logger::setLogLevel(brls::LogLevel::INFO);
if (!brls::Application::init()) {
brls::Logger::error("Unable to init borealis application");
return EXIT_FAILURE;
}
brls::Application::createWindow("Benchmark Toolbox");
brls::Application::setGlobalQuit(true);
brls::Application::pushActivity(new MainActivity());
while (brls::Application::mainLoop())
;
_exit(EXIT_SUCCESS);
}