Files
Horizon-OC-pro/Source/Benchmark-Toolbox/source/memtester/mt_gpu.cpp
2026-06-11 18:56:01 -04:00

337 lines
12 KiB
C++

#include "mt_gpu.h"
#include <atomic>
#include <thread>
#include <switch.h>
#include <EGL/egl.h>
#include <glad/glad.h>
namespace {
const char *SH_FILL =
"#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"
"uniform uint var;\n"
"void main() {\n"
" src[gl_GlobalInvocationID.x] = var + gl_GlobalInvocationID.x;\n"
"}\n";
const char *SH_VERIFY_FILL =
"#version 430\n"
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
"layout(std430, binding = 2) buffer resBuffer { uint res; };\n"
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
"uniform uint var;\n"
"void main() {\n"
" if (src[gl_GlobalInvocationID.x] != (var + gl_GlobalInvocationID.x)) {\n"
" res = 0x55555555u;\n"
" }\n"
"}\n";
const char *SH_COPY =
"#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";
const char *SH_VERIFY_COPY =
"#version 430\n"
"layout(std430, binding = 0) buffer srcBuffer { volatile uint src[]; };\n"
"layout(std430, binding = 1) buffer dstBuffer { volatile uint dst[]; };\n"
"layout(std430, binding = 2) buffer resBuffer { uint res; };\n"
"layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;\n"
"void main() {\n"
" if (src[gl_GlobalInvocationID.x] != dst[gl_GlobalInvocationID.x]) {\n"
" res = 0x55555555u;\n"
" }\n"
"}\n";
std::thread g_thread;
std::atomic<bool> g_stop{ false };
std::atomic<bool> g_running{ false };
std::atomic<bool> g_error{ false };
std::atomic<uint64_t> g_loop{ 0 };
std::atomic<uint64_t> g_mismatches{ 0 };
std::atomic<uint64_t> g_size_mb{ 0 };
const char *volatile g_status = "Stopped";
EGLDisplay s_dpy = EGL_NO_DISPLAY;
EGLContext s_ctx = EGL_NO_CONTEXT;
uint32_t s_rng_a = 0x12345, s_rng_b = 0x34211, s_rng_c = 0x57a3f, s_rng_d = 0x9e3779;
uint32_t nextRandom() {
s_rng_a = (s_rng_a & 0x3ffe) << 18 | (s_rng_a ^ s_rng_a << 6) >> 13;
s_rng_b = (s_rng_b << 2 & 0xffffffe0) | (s_rng_b << 2 ^ s_rng_b) >> 27;
s_rng_c = (s_rng_c & 0x1fffff0) << 7 | (s_rng_c ^ s_rng_c << 13) >> 21;
s_rng_d = (s_rng_d & 0x7ff80) << 13 | (s_rng_d ^ s_rng_d << 3) >> 12;
return s_rng_a ^ s_rng_b ^ s_rng_c ^ s_rng_d;
}
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_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;
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);
eglTerminate(s_dpy);
}
s_ctx = EGL_NO_CONTEXT;
s_dpy = EGL_NO_DISPLAY;
eglReleaseThread();
}
GLuint buildProgram(const char *src) {
GLuint sh = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(sh, 1, &src, nullptr);
glCompileShader(sh);
GLint ok = 0;
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
if (!ok) {
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;
}
void worker(bool full) {
if (!eglUp()) {
g_status = "EGL init failed";
g_error = true;
eglDown();
g_running.store(false);
return;
}
gladLoadGLLoader((GLADloadproc)eglGetProcAddress);
GLint maxSsbo = 0;
glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxSsbo);
if (maxSsbo <= 0) {
g_status = "Failed to query SSBO size";
g_error = true;
eglDown();
g_running.store(false);
return;
}
size_t cap = full ? (size_t)0x20000000 : (size_t)0x8000000;
size_t want = full ? ((size_t)maxSsbo << 2) : (size_t)maxSsbo;
size_t bytes = (want < cap + 1) ? (want & ~(size_t)0x3ff) : cap;
if (bytes < 0x400) {
g_status = "GPU memtester buffer too small";
g_error = true;
eglDown();
g_running.store(false);
return;
}
g_size_mb.store((uint64_t)(bytes >> 20));
GLuint groups = (GLuint)(bytes >> 10);
GLuint fill = buildProgram(SH_FILL);
GLuint verifyFill = buildProgram(SH_VERIFY_FILL);
GLuint copy = buildProgram(SH_COPY);
GLuint verifyCopy = buildProgram(SH_VERIFY_COPY);
if (!fill || !verifyFill || !copy || !verifyCopy) {
g_status = "Compute program build failed";
g_error = true;
if (fill) glDeleteProgram(fill);
if (verifyFill) glDeleteProgram(verifyFill);
if (copy) glDeleteProgram(copy);
if (verifyCopy) glDeleteProgram(verifyCopy);
eglDown();
g_running.store(false);
return;
}
GLint fillVarLoc = glGetUniformLocation(fill, "var");
GLint verifyVarLoc = glGetUniformLocation(verifyFill, "var");
GLuint bufs[3] = { 0, 0, 0 };
glGenBuffers(3, bufs);
const GLuint resInit = 0;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[0]);
glBufferData(GL_SHADER_STORAGE_BUFFER, bytes, nullptr, GL_DYNAMIC_COPY);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[1]);
glBufferData(GL_SHADER_STORAGE_BUFFER, bytes, nullptr, GL_DYNAMIC_COPY);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[2]);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLuint), &resInit, GL_DYNAMIC_COPY);
if (glGetError() != GL_NO_ERROR) {
g_status = "Failed to allocate GPU memtester buffers";
g_error = true;
glDeleteBuffers(3, bufs);
glDeleteProgram(fill);
glDeleteProgram(verifyFill);
glDeleteProgram(copy);
glDeleteProgram(verifyCopy);
eglDown();
g_running.store(false);
return;
}
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, bufs[0]);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, bufs[1]);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, bufs[2]);
g_status = "Running";
while (!g_stop.load()) {
uint32_t var = nextRandom();
glUseProgram(fill);
if (fillVarLoc >= 0)
glUniform1ui(fillVarLoc, var);
glDispatchCompute(groups, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
glFinish();
if (glGetError() != GL_NO_ERROR) {
g_status = "GPU error during GPU memtester";
g_error = true;
break;
}
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[2]);
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint), &resInit);
glUseProgram(verifyFill);
if (verifyVarLoc >= 0)
glUniform1ui(verifyVarLoc, var);
glDispatchCompute(groups, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
glFinish();
{
GLuint res = 0;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[2]);
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint), &res);
if (res == 0x55555555u)
g_mismatches.fetch_add(1);
}
if (g_stop.load())
break;
glUseProgram(copy);
glDispatchCompute(groups, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
glFinish();
if (glGetError() != GL_NO_ERROR) {
g_status = "GPU error during GPU memtester";
g_error = true;
break;
}
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[2]);
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint), &resInit);
glUseProgram(verifyCopy);
glDispatchCompute(groups, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
glFinish();
{
GLuint res = 0;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufs[2]);
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint), &res);
if (res == 0x55555555u)
g_mismatches.fetch_add(1);
}
g_loop.fetch_add(1);
}
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
glUseProgram(0);
glDeleteBuffers(3, bufs);
glDeleteProgram(copy);
glDeleteProgram(verifyCopy);
glDeleteProgram(verifyFill);
glDeleteProgram(fill);
eglDown();
if (!g_error.load())
g_status = "Stopped";
g_running.store(false);
}
} // namespace
extern "C" void mt_gpu_start(int full) {
if (g_running.load())
return;
if (g_thread.joinable())
g_thread.join();
g_stop.store(false);
g_error.store(false);
g_loop.store(0);
g_mismatches.store(0);
g_size_mb.store(0);
g_status = "Preparing GPU memtester...";
g_running.store(true);
appletSetAutoSleepDisabled(true);
g_thread = std::thread(worker, full != 0);
}
extern "C" void mt_gpu_stop(void) {
g_stop.store(true);
if (g_thread.joinable())
g_thread.join();
g_running.store(false);
appletSetAutoSleepDisabled(false);
}
extern "C" int mt_gpu_running(void) {
return g_running.load() ? 1 : 0;
}
extern "C" void mt_gpu_get(mt_gpu_status_t *out) {
if (!out)
return;
out->loop = g_loop.load();
out->mismatches = g_mismatches.load();
out->size_mb = g_size_mb.load();
out->running = g_running.load() ? 1 : 0;
out->error = g_error.load() ? 1 : 0;
out->status = g_status;
}