taisei/src/renderer/gl33/framebuffer_async_read.c
2024-09-03 01:52:50 +02:00

173 lines
4.4 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "framebuffer_async_read.h"
#include "framebuffer.h"
#include "../glcommon/texture.h"
#include "gl33.h"
#include "opengl.h"
#include "util.h"
typedef struct FramebufferReadRequest {
FramebufferReadAsyncCallback callback;
void *userdata;
GLuint pbo;
GLsync sync;
GLenum sync_result;
GLbitfield sync_flags;
struct {
uint width;
uint height;
PixmapFormat format;
} transfer;
} FramebufferReadRequest;
static FramebufferReadRequest read_requests[4];
static void handle_read_request(FramebufferReadRequest *rq, bool ok) {
glDeleteSync(NOT_NULL(rq->sync));
rq->sync = NULL;
rq->sync_result = GL_NONE;
if(ok) {
assert(rq->pbo != 0);
auto prev_pbo = gl33_buffer_current(GL33_BUFFER_BINDING_PIXEL_PACK);
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_PACK, rq->pbo);
gl33_sync_buffer(GL33_BUFFER_BINDING_PIXEL_PACK);
auto data_size = pixmap_data_size(rq->transfer.format, rq->transfer.width, rq->transfer.height);
rq->callback(&(Pixmap) {
.width = rq->transfer.width,
.height = rq->transfer.height,
.format = rq->transfer.format,
.origin = PIXMAP_ORIGIN_BOTTOMLEFT,
.data_size = data_size,
.data.untyped = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, data_size, GL_MAP_READ_BIT)
}, rq->userdata);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_PACK, prev_pbo);
// TODO invalidate
} else {
rq->callback(NULL, rq->userdata);
}
}
static GLenum sync_read_request(FramebufferReadRequest *rq, GLuint64 timeout) {
if(!rq->sync) {
return GL_NONE;
}
rq->sync_result = glClientWaitSync(rq->sync, rq->sync_flags, timeout);
rq->sync_flags = 0;
switch(rq->sync_result) {
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
handle_read_request(rq, true);
break;
case GL_WAIT_FAILED:
handle_read_request(rq, false);
break;
case GL_TIMEOUT_EXPIRED:
break;
default: UNREACHABLE;
}
return rq->sync_result;
}
static FramebufferReadRequest *alloc_read_request_timeout(GLuint64 timeout) {
for(int i = 0; i < ARRAY_SIZE(read_requests); ++i) {
auto rq = read_requests + i;
if(sync_read_request(rq, timeout) == GL_NONE) {
return rq;
}
}
return NULL;
}
static FramebufferReadRequest *alloc_read_request(void) {
auto rq = alloc_read_request_timeout(0);
if(rq) {
return rq;
}
log_warn("Queue is full, forcing synchronization");
rq = alloc_read_request_timeout(UINT64_MAX);
return NOT_NULL(rq);
}
void gl33_framebuffer_process_read_requests(void) {
for(int i = 0; i < ARRAY_SIZE(read_requests); ++i) {
sync_read_request(read_requests + i, 0);
}
}
void gl33_framebuffer_finalize_read_requests(void) {
gl33_framebuffer_process_read_requests();
for(int i = 0; i < ARRAY_SIZE(read_requests); ++i) {
auto rq = read_requests + i;
sync_read_request(rq, UINT64_MAX);
if(rq->sync) {
glDeleteSync(rq->sync);
rq->sync = NULL;
rq->sync_result = GL_NONE;
}
if(rq->pbo) {
glDeleteBuffers(1, &rq->pbo);
rq->pbo = 0;
}
}
}
void gl33_framebuffer_read_async(
Framebuffer *framebuffer,
FramebufferAttachment attachment,
IntRect region,
void *userdata,
FramebufferReadAsyncCallback callback
) {
GLTextureFormatInfo *fmtinfo = gl33_framebuffer_get_format(framebuffer, attachment);
auto rq = alloc_read_request();
rq->transfer.width = region.w;
rq->transfer.height = region.h;
rq->transfer.format = fmtinfo->transfer_format.pixmap_format;
rq->userdata = userdata;
rq->callback = callback;
rq->sync_flags = GL_SYNC_FLUSH_COMMANDS_BIT;
if(!rq->pbo) {
glGenBuffers(1, &rq->pbo);
}
auto prev_pbo = gl33_buffer_current(GL33_BUFFER_BINDING_PIXEL_PACK);
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_PACK, rq->pbo);
gl33_sync_buffer(GL33_BUFFER_BINDING_PIXEL_PACK);
auto data_size = pixmap_data_size(rq->transfer.format, rq->transfer.width, rq->transfer.height);
glBufferData(GL_PIXEL_PACK_BUFFER, data_size, NULL, GL_STREAM_READ);
gl33_framebuffer_bind_for_read(framebuffer, attachment);
glReadPixels(
region.x, region.y, region.w, region.h,
fmtinfo->transfer_format.gl_format, fmtinfo->transfer_format.gl_type,
NULL
);
rq->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
rq->sync_flags = GL_SYNC_FLUSH_COMMANDS_BIT;
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_PACK, prev_pbo);
}