mirror of
https://github.com/mitch030504/AstroQuest-Frame.git
synced 2026-10-18 16:01:03 +02:00
On top of the shadps4-arm64 core imported in the previous commit: - PlayStation VR emulation: headset, tracker, reprojection, camera and social screen; the player's seat and view reset; head velocity for head-butts; field of view settings - Sound: a native Ngs2 engine (samplers, submixers, effects), the Audio3d spatializer, virtual surround for headset speakers, microphone pass-through; libSceJson2; libSceFiber on FEX - Fixes for CUSA12392 under emulation: real-time step, pace and resolution governor, larger render sizes on the PC, save loading at start - Renderer: geometry shaders fed with fans/polygons/loops, cube-array layers of light probes, sample-count mismatches on Adreno, timeline semaphore hang, FXAA in place of the MSAA resolve, sharpening, VR frame export - quest-host: the Quest app (OpenXR host that runs the glibc core as a child process, frames through AHardwareBuffers, DualSense mapping, hand-tracked controller placement, KGSL shim) - PC: in-process OpenXR host (Virtual Desktop or any OpenXR runtime), Touch controllers as the gamepad, launcher with a settings window; vendored OpenXR SDK 1.1.63 - Builds keep the build machine's folders out of the binaries (-ffile-prefix-map); machine settings (Python, headset serial, signing key) live in git-ignored tools/*.local files - tools: build, packaging and test scripts for Windows and the headset - README with installation, license (GPL-2.0-or-later), third-party notices and thanks No game files are part of the repository; .gitignore only lets listed paths in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
257 lines
9.0 KiB
C++
257 lines
9.0 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "gl_frames.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include <android/hardware_buffer.h>
|
|
#include <dlfcn.h>
|
|
|
|
#include "core_process.h"
|
|
#include "log.h"
|
|
|
|
namespace {
|
|
|
|
/// How gralloc describes a buffer; the first file descriptor is the dma-buf with the pixels.
|
|
struct NativeHandle {
|
|
int version;
|
|
int num_fds;
|
|
int num_ints;
|
|
int data[1];
|
|
};
|
|
|
|
GLuint CompileShader(GLenum type, const char* source) {
|
|
const GLuint shader = glCreateShader(type);
|
|
glShaderSource(shader, 1, &source, nullptr);
|
|
glCompileShader(shader);
|
|
GLint ok = GL_FALSE;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
|
if (ok != GL_TRUE) {
|
|
char log[512];
|
|
glGetShaderInfoLog(shader, sizeof(log), nullptr, log);
|
|
LOGE("shader compilation failed: %s", log);
|
|
}
|
|
return shader;
|
|
}
|
|
|
|
const char* const BlitVertexShader = R"(#version 300 es
|
|
out vec2 uv;
|
|
void main() {
|
|
vec2 corner = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
// The frame's first row is its top row, GL's first row is the bottom one.
|
|
uv = vec2(corner.x, 1.0 - corner.y);
|
|
gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0);
|
|
}
|
|
)";
|
|
|
|
const char* const BlitFragmentShader = R"(#version 300 es
|
|
precision highp float;
|
|
uniform sampler2D frame;
|
|
uniform int swap_red_blue;
|
|
uniform int decode_srgb;
|
|
in vec2 uv;
|
|
out vec4 color;
|
|
void main() {
|
|
vec3 c = texture(frame, uv).rgb;
|
|
if (swap_red_blue != 0) {
|
|
c = c.bgr;
|
|
}
|
|
// The emulator delivers display-ready values. An sRGB target encodes whatever is written to
|
|
// it, so those have to be made linear first.
|
|
if (decode_srgb != 0) {
|
|
c = mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));
|
|
}
|
|
color = vec4(c, 1.0);
|
|
}
|
|
)";
|
|
|
|
} // namespace
|
|
|
|
bool GlContext::Create() {
|
|
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if (display == EGL_NO_DISPLAY || !eglInitialize(display, nullptr, nullptr)) {
|
|
LOGE("eglInitialize failed");
|
|
return false;
|
|
}
|
|
const EGLint config_attributes[] = {
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
|
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 0, EGL_NONE,
|
|
};
|
|
EGLint num_configs = 0;
|
|
if (!eglChooseConfig(display, config_attributes, &config, 1, &num_configs) ||
|
|
num_configs < 1) {
|
|
LOGE("eglChooseConfig failed");
|
|
return false;
|
|
}
|
|
const EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
|
|
context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes);
|
|
// Nothing is drawn to this surface, the context just needs one to be current.
|
|
const EGLint surface_attributes[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
|
|
surface = eglCreatePbufferSurface(display, config, surface_attributes);
|
|
if (context == EGL_NO_CONTEXT || surface == EGL_NO_SURFACE ||
|
|
!eglMakeCurrent(display, surface, surface, context)) {
|
|
LOGE("unable to create the GL context: 0x%x", eglGetError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GlContext::Destroy() {
|
|
if (display == EGL_NO_DISPLAY) {
|
|
return;
|
|
}
|
|
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
if (surface != EGL_NO_SURFACE) {
|
|
eglDestroySurface(display, surface);
|
|
}
|
|
if (context != EGL_NO_CONTEXT) {
|
|
eglDestroyContext(display, context);
|
|
}
|
|
eglTerminate(display);
|
|
display = EGL_NO_DISPLAY;
|
|
surface = EGL_NO_SURFACE;
|
|
context = EGL_NO_CONTEXT;
|
|
}
|
|
|
|
bool FrameBuffers::Create(EGLDisplay display, uint32_t width, uint32_t height,
|
|
CoreProcess& core) {
|
|
using GetNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
|
|
const auto get_native_handle = reinterpret_cast<GetNativeHandle>(
|
|
dlsym(RTLD_DEFAULT, "AHardwareBuffer_getNativeHandle"));
|
|
if (get_native_handle == nullptr) {
|
|
LOGE("AHardwareBuffer_getNativeHandle is not available");
|
|
return false;
|
|
}
|
|
|
|
AHardwareBuffer_Desc description{};
|
|
description.width = width;
|
|
description.height = height;
|
|
description.layers = 1;
|
|
description.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
|
// The CPU bits keep the buffer in a plain row-by-row layout, which is what the emulator's
|
|
// GPU driver can attach to and what its fallback path writes.
|
|
description.usage =
|
|
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER |
|
|
AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY;
|
|
|
|
std::vector<int> fds;
|
|
uint32_t stride = 0;
|
|
for (uint32_t i = 0; i < Count; ++i) {
|
|
AHardwareBuffer* buffer = nullptr;
|
|
if (AHardwareBuffer_allocate(&description, &buffer) != 0) {
|
|
LOGE("unable to allocate frame buffer %u", i);
|
|
return false;
|
|
}
|
|
buffers.push_back(buffer);
|
|
AHardwareBuffer_Desc actual{};
|
|
AHardwareBuffer_describe(buffer, &actual);
|
|
stride = actual.stride;
|
|
|
|
const NativeHandle* handle = get_native_handle(buffer);
|
|
if (handle == nullptr || handle->num_fds < 1) {
|
|
LOGE("frame buffer %u has no file descriptor", i);
|
|
return false;
|
|
}
|
|
fds.push_back(handle->data[0]);
|
|
|
|
const EGLClientBuffer client_buffer = eglGetNativeClientBufferANDROID(buffer);
|
|
const EGLint image_attributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
|
|
const EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
|
|
EGL_NATIVE_BUFFER_ANDROID, client_buffer,
|
|
image_attributes);
|
|
if (image == EGL_NO_IMAGE_KHR) {
|
|
LOGE("unable to wrap frame buffer %u in an EGL image: 0x%x", i, eglGetError());
|
|
return false;
|
|
}
|
|
images.push_back(image);
|
|
|
|
GLuint texture = 0;
|
|
glGenTextures(1, &texture);
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
|
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_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
textures.push_back(texture);
|
|
}
|
|
LOGI("frame buffers: %u x %ux%u, stride %u", Count, width, height, stride);
|
|
core.SetBuffers(fds, width, height, stride);
|
|
return true;
|
|
}
|
|
|
|
void FrameBuffers::Destroy(EGLDisplay display) {
|
|
for (const GLuint texture : textures) {
|
|
glDeleteTextures(1, &texture);
|
|
}
|
|
for (const EGLImageKHR image : images) {
|
|
eglDestroyImageKHR(display, image);
|
|
}
|
|
for (AHardwareBuffer* buffer : buffers) {
|
|
AHardwareBuffer_release(buffer);
|
|
}
|
|
textures.clear();
|
|
images.clear();
|
|
buffers.clear();
|
|
}
|
|
|
|
bool FrameBlitter::Create() {
|
|
const GLuint vertex = CompileShader(GL_VERTEX_SHADER, BlitVertexShader);
|
|
const GLuint fragment = CompileShader(GL_FRAGMENT_SHADER, BlitFragmentShader);
|
|
program = glCreateProgram();
|
|
glAttachShader(program, vertex);
|
|
glAttachShader(program, fragment);
|
|
glLinkProgram(program);
|
|
glDeleteShader(vertex);
|
|
glDeleteShader(fragment);
|
|
GLint linked = GL_FALSE;
|
|
glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
|
if (linked != GL_TRUE) {
|
|
LOGE("unable to link the blit program");
|
|
return false;
|
|
}
|
|
uniform_swap = glGetUniformLocation(program, "swap_red_blue");
|
|
uniform_decode = glGetUniformLocation(program, "decode_srgb");
|
|
glGenVertexArrays(1, &vertex_array);
|
|
const char* extensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
|
|
write_control =
|
|
extensions != nullptr && std::strstr(extensions, "GL_EXT_sRGB_write_control") != nullptr;
|
|
return true;
|
|
}
|
|
|
|
void FrameBlitter::Destroy() {
|
|
if (program != 0) {
|
|
glDeleteProgram(program);
|
|
program = 0;
|
|
}
|
|
if (vertex_array != 0) {
|
|
glDeleteVertexArrays(1, &vertex_array);
|
|
vertex_array = 0;
|
|
}
|
|
}
|
|
|
|
void FrameBlitter::Draw(GLuint texture, uint32_t width, uint32_t height, bool swap_red_blue,
|
|
bool srgb_target) const {
|
|
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
const bool unencoded = srgb_target && write_control;
|
|
if (unencoded) {
|
|
glDisable(GL_FRAMEBUFFER_SRGB_EXT);
|
|
}
|
|
glUseProgram(program);
|
|
glUniform1i(uniform_swap, swap_red_blue ? 1 : 0);
|
|
glUniform1i(uniform_decode, srgb_target && !write_control ? 1 : 0);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
glBindVertexArray(vertex_array);
|
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
if (unencoded) {
|
|
// As it is by default: everything else drawn to such a target counts on it.
|
|
glEnable(GL_FRAMEBUFFER_SRGB_EXT);
|
|
}
|
|
}
|