Rewrite the GL renderer to remove the GL4.x requirements.

This commit is contained in:
Lionel Flandrin 2016-12-11 22:13:05 +01:00
parent df1262004d
commit 90d7f63947
6 changed files with 174 additions and 192 deletions

View File

@ -2197,7 +2197,7 @@ static bool glsm_state_ctx_init(void *data)
#else
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
#endif
#else
#else /* HAVE_OPENGLES */
#ifdef CORE
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL_CORE;
hw_render.version_major = 3;
@ -2205,7 +2205,7 @@ static bool glsm_state_ctx_init(void *data)
#else
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
#endif
#endif
#endif /* HAVE_OPENGLES */
hw_render.context_reset = params->context_reset;
hw_render.context_destroy = params->context_destroy;
hw_render.stencil = params->stencil;

View File

@ -241,7 +241,7 @@ void GlRenderer::draw()
this->command_buffer->program->uniform1ui("draw_semi_transparent", 0);
this->command_buffer->pre_bind();
this->command_buffer->prepare_draw();
GLushort *opaque_triangle_indices =
this->opaque_triangle_indices + this->opaque_triangle_index_pos + 1;
@ -249,9 +249,9 @@ void GlRenderer::draw()
INDEX_BUFFER_LEN - this->opaque_triangle_index_pos - 1;
if (opaque_triangle_len) {
this->command_buffer->draw_indexed_no_bind(GL_TRIANGLES,
opaque_triangle_indices,
opaque_triangle_len);
this->command_buffer->draw_indexed__raw(GL_TRIANGLES,
opaque_triangle_indices,
opaque_triangle_len);
}
GLushort *opaque_line_indices =
@ -260,9 +260,9 @@ void GlRenderer::draw()
INDEX_BUFFER_LEN - this->opaque_line_index_pos - 1;
if (opaque_line_len) {
this->command_buffer->draw_indexed_no_bind(GL_LINES,
opaque_line_indices,
opaque_line_len);
this->command_buffer->draw_indexed__raw(GL_LINES,
opaque_line_indices,
opaque_line_len);
}
if (this->semi_transparent_index_pos > 0) {
@ -325,15 +325,16 @@ void GlRenderer::draw()
unsigned len = it->last_index - cur_index;
GLushort *indices = this->semi_transparent_indices + cur_index;
this->command_buffer->draw_indexed_no_bind(it->draw_mode,
indices,
len);
this->command_buffer->draw_indexed__raw(it->draw_mode,
indices,
len);
cur_index = it->last_index;
}
}
this->command_buffer->finish();
this->command_buffer->finalize_draw__no_bind();
this->primitive_ordering = 0;
this->opaque_triangle_index_pos = INDEX_BUFFER_LEN - 1;
@ -463,7 +464,6 @@ void GlRenderer::upload_textures(uint16_t top_left[2],
Framebuffer _fb = Framebuffer(this->fb_out);
this->image_load_buffer->draw(GL_TRIANGLE_STRIP);
this->image_load_buffer->swap();
glPolygonMode(GL_FRONT_AND_BACK, this->command_polygon_mode);
glEnable(GL_SCISSOR_TEST);
@ -510,7 +510,6 @@ void GlRenderer::upload_vram_window(uint16_t top_left[2],
Framebuffer _fb = Framebuffer(this->fb_out);
this->image_load_buffer->draw(GL_TRIANGLE_STRIP);
this->image_load_buffer->swap();
glPolygonMode(GL_FRONT_AND_BACK, this->command_polygon_mode);
glEnable(GL_SCISSOR_TEST);
@ -749,7 +748,6 @@ void GlRenderer::finalize_frame()
this->output_buffer->program->uniform1ui( "internal_upscaling",
this->internal_upscaling);
this->output_buffer->draw(GL_TRIANGLE_STRIP);
this->output_buffer->swap();
}
// Hack: copy fb_out back into fb_texture at the end of every
@ -779,7 +777,6 @@ void GlRenderer::finalize_frame()
this->image_load_buffer->draw(GL_TRIANGLE_STRIP);
this->image_load_buffer->swap();
}
// Cleanup OpenGL context before returning to the frontend
@ -869,7 +866,6 @@ void GlRenderer::vertex_preprocessing(CommandVertex *v,
if (buffer_full) {
this->draw();
this->command_buffer->swap();
}
int16_t z = this->primitive_ordering;
@ -910,8 +906,11 @@ void GlRenderer::push_quad(CommandVertex v[4],
this->vertex_preprocessing(v, 4, GL_TRIANGLES, stm);
// The diagonal is duplicated
static const GLushort indices[6] = {0, 1, 2, 1, 2, 3};
// The diagonal is duplicated. I originally used "1, 2, 1, 2" to
// duplicate the diagonal but I believe it was incorrect because of
// the OpenGL filling convention. At least it's what TinyTiger told
// me...
static const GLushort indices[6] = {0, 1, 2, 2, 1, 3};
unsigned index = this->command_buffer->next_index();
@ -1048,13 +1047,26 @@ void GlRenderer::copy_rect( uint16_t source_top_left[2],
GLsizei w = (GLsizei) dimensions[0] * (GLsizei) upscale;
GLsizei h = (GLsizei) dimensions[1] * (GLsizei) upscale;
// XXX CopyImageSubData gives undefined results if the source
// and target area overlap, this should be handled
// explicitely
/* TODO - OpenGL 4.3 and GLES 3.2 requirement! FIXME! */
glCopyImageSubData( this->fb_out->id, GL_TEXTURE_2D, 0, src_x, src_y, 0,
this->fb_out->id, GL_TEXTURE_2D, 0, dst_x, dst_y, 0,
w, h, 1 );
GLuint fb;
glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb);
glFramebufferTexture(GL_READ_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
this->fb_out->id,
0);
glReadBuffer(GL_COLOR_ATTACHMENT0);
// Can I bind the same texture to the framebuffer and
// GL_TEXTURE_2D? Something tells me this is undefined
// behaviour. I could use glReadPixels and glWritePixels instead
// or something like that.
glBindTexture(GL_TEXTURE_2D, this->fb_out->id);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, dst_x, dst_y, src_x, src_y, w, h);
glDeleteFramebuffers(1, &fb);
get_error();
}

View File

@ -30,8 +30,10 @@ const uint16_t VRAM_WIDTH_PIXELS = 1024;
const uint16_t VRAM_HEIGHT = 512;
const size_t VRAM_PIXELS = (size_t) VRAM_WIDTH_PIXELS * (size_t) VRAM_HEIGHT;
/// How many vertices we buffer before forcing a draw
static const unsigned int VERTEX_BUFFER_LEN = 0x8000;
/// How many vertices we buffer before forcing a draw. Since the
/// indexes are stored on 16bits we need to make sure that the length
/// multiplied by 3 (for triple buffering) doesn't overflow 0xffff.
static const unsigned int VERTEX_BUFFER_LEN = 0x4000;
/// Maximum number of indices for a vertex buffer. Since quads have
/// two duplicated vertices it can be up to 3/2 the vertex buffer
/// length
@ -49,7 +51,7 @@ struct DrawConfig {
struct CommandVertex {
/// Position in PlayStation VRAM coordinates
float position[4];
float position[4];
/// RGB color, 8bits per component
uint8_t color[3];
/// Texture coordinates within the page

View File

@ -4,58 +4,17 @@
#include <glsm/glsmsym.h>
#include <stdio.h>
#include <stdlib.h> // size_t
#include <stdlib.h>
#include <stdint.h>
//#include <unistd.h>
#include <vector>
#include <deque>
#include <cassert>
#include "vertex.h"
#include "program.h"
#include "error.h"
template<typename T>
struct Storage {
// Fence used to make sure we're not writing to the buffer while
// it's being used.
GLsync fence;
// Offset in the main buffer
unsigned offset;
Storage()
:fence(NULL), offset(0)
{
}
Storage(unsigned offset)
:fence(NULL), offset(offset)
{
}
~Storage() {
if (this->fence) {
glDeleteSync(this->fence);
}
}
// Wait for the buffer to be ready for reuse
void sync() {
if (this->fence) {
glWaitSync(this->fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(this->fence);
this->fence = NULL;
get_error();
}
}
void create_fence() {
void *fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
this->fence = reinterpret_cast<GLsync>(fence);
}
};
template<typename T>
class DrawBuffer
{
@ -68,20 +27,19 @@ public:
VertexArrayObject* vao;
/// Program used to draw this buffer
Program* program;
/// Persistently mapped buffer (using ARB_buffer_storage)
/// Currently mapped buffer range (write-only)
T *map;
/// Use triple buffering
Storage<T> buffers[3];
unsigned active_buffer;
/// Index one-past the last element stored in `active`
unsigned active_next_index;
/// Index of the first element of the current command in `active`
unsigned active_command_index;
/// Number of elements T that `active` and `backed` can hold
/// Number of elements T mapped at once in `map`
size_t capacity;
/// Index one-past the last element stored in `map`, relative to
/// the first element in `map`
size_t map_index;
/// Absolute offset of the 1st mapped element in the current
/// buffer relative to the beginning of the GL storage.
size_t map_start;
DrawBuffer(size_t capacity, Program* program)
:map(NULL)
{
VertexArrayObject* vao = new VertexArrayObject();
@ -97,36 +55,22 @@ public:
// Create and map the buffer
this->bind();
size_t element_size = sizeof(T);
// We double buffer so we allocate a storage twise as big
GLsizeiptr storage_size = (GLsizeiptr) (this->capacity * element_size * 3);
// We allocate enough space for 3 times the buffer space and
// we only remap one third of it at a time
GLsizeiptr storage_size = this->capacity * element_size * 3;
glBufferStorage(GL_ARRAY_BUFFER,
storage_size,
NULL,
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT |
GL_MAP_COHERENT_BIT);
// Since we store indexes in unsigned shorts we want to make
// sure the entire buffer is indexable.
assert(this->capacity * 3 <= 0xffff);
glBufferData(GL_ARRAY_BUFFER, storage_size, NULL, GL_DYNAMIC_DRAW);
this->bind_attributes();
void *m = glMapBufferRange(GL_ARRAY_BUFFER,
0,
storage_size,
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT |
GL_MAP_FLUSH_EXPLICIT_BIT |
GL_MAP_COHERENT_BIT);
this->map_index = 0;
this->map_start = 0;
this->map = reinterpret_cast<T*>(m);
this->buffers[0] = Storage<T>(0);
this->buffers[1] = Storage<T>(this->capacity);
this->buffers[2] = Storage<T>(this->capacity * 2);
this->active_buffer = 0;
this->active_next_index = 0;
this->active_command_index = 0;
this->map__no_bind();
get_error();
}
@ -135,10 +79,7 @@ public:
{
this->bind();
this->buffers[1].sync();
this->buffers[2].sync();
glUnmapBuffer(GL_ARRAY_BUFFER);
this->unmap__no_bind();
glDeleteBuffers(1, &this->id);
@ -153,12 +94,6 @@ public:
}
}
struct Storage<T> *get_active_buffer()
{
return &this->buffers[this->active_buffer];
}
/* fn bind_attributes(&self)-> Result<(), Error> { */
void bind_attributes()
{
this->vao->bind();
@ -170,20 +105,6 @@ public:
GLint element_size = (GLint) sizeof( T );
/*
let index =
match self.program.find_attribute(attr.name) {
Ok(i) => i,
// Don't error out if the shader doesn't use this
// attribute, it could be caused by shader
// optimization if the attribute is unused for
// some reason.
Err(Error::InvalidValue) => continue,
Err(e) => return Err(e),
};
*/
//speculative: attribs enabled on VAO=0 (disabled) get applied to the VAO when created initially
//as a core, we don't control the state entirely at this point. frontend may have enabled attribs.
//we need to make sure they're all disabled before then re-enabling the attribs we want
@ -241,22 +162,60 @@ public:
get_error();
}
unsigned next_index() {
return this->active_next_index;
// Map the buffer for write-only access
void map__no_bind()
{
size_t element_size = sizeof(T);
GLsizeiptr buffer_size = this->capacity * element_size;
GLintptr offset_bytes;
void *m;
this->bind();
// If we're already mapped something's wrong
assert(this->map == NULL);
if (this->map_start > 2 * this->capacity) {
// We don't have enough room left to remap `capacity`,
// start back from the beginning of the buffer.
this->map_start = 0;
}
offset_bytes = this->map_start * element_size;
printf("Remap %lu %lu\n", this->capacity, this->map_start);
m = glMapBufferRange(GL_ARRAY_BUFFER,
offset_bytes,
buffer_size,
GL_MAP_WRITE_BIT |
GL_MAP_INVALIDATE_RANGE_BIT);
get_error();
// Just in case...
assert(m != NULL);
this->map = reinterpret_cast<T *>(m);
}
/// Swap the active and backed buffers
void swap() {
this->get_active_buffer()->create_fence();
// Unmap the active buffer
void unmap__no_bind()
{
assert(this->map != NULL);
if (++this->active_buffer > 2) {
this->active_buffer = 0;
}
this->bind();
this->get_active_buffer()->sync();
glUnmapBuffer(GL_ARRAY_BUFFER);
this->active_next_index = 0;
this->active_command_index = 0;
this->map = NULL;
}
/// Returns the index of the next item to be inserted in
/// `map`. Can be used to build an index buffer for indexed draws.
size_t next_index()
{
return this->map_start + this->map_index;
}
void enable_attribute(const char* attr)
@ -289,16 +248,10 @@ public:
get_error();
}
/// Return true if `map` is empty
bool empty()
{
return this->active_next_index == this->active_command_index;
}
/// Called when the current batch is completed (the draw calls
/// have been done and we won't reference that data anymore)
void finish()
{
this->active_command_index = this->active_next_index;
return this->map_index == 0;
}
/// Bind the buffer to the current VAO
@ -307,69 +260,84 @@ public:
glBindBuffer(GL_ARRAY_BUFFER, this->id);
}
/// Push new vertices in the storage. If `n` is greater than
/// `remaining_capacity` this function crashes, it's up to the
/// caller to make sure there's enough room left.
void push_slice(T slice[], size_t n)
{
if (n > this->remaining_capacity() ) {
printf("DrawBuffer::push_slice() - Out of memory \n");
return;
}
assert(n <= this->remaining_capacity());
assert(this->map != NULL);
struct Storage<T> *buffer = this->get_active_buffer();
memcpy(this->map + buffer->offset + this->active_next_index,
memcpy(this->map + this->map_index,
slice,
n * sizeof(T));
this->active_next_index += n;
this->map_index += n;
}
/// Prepares the buffer for a draw command. We have to bind the
/// VAO, the program and unmap the buffer.
void prepare_draw()
{
this->vao->bind();
this->program->bind();
// I don't need to bind this to draw (it's captured by the
// VAO) but I need it to map/unmap the storage.
this->bind();
this->unmap__no_bind();
}
/// Finalize the current buffer data and remap a fresh slice of
/// the storage.
void finalize_draw__no_bind()
{
this->map_start += this->map_index;
this->map_index = 0;
this->map__no_bind();
}
void draw(GLenum mode)
{
printf("Draw %lu\n", this->map_index);
if (this->empty()) {
return;
}
this->vao->bind();
this->program->bind();
struct Storage<T> *buffer = this->get_active_buffer();
unsigned start = buffer->offset + this->active_command_index;
unsigned len = this->active_next_index - this->active_command_index;
this->prepare_draw();
// Length in number of vertices
glDrawArrays(mode, start, len);
glDrawArrays(mode, this->map_start, this->map_index);
this->finalize_draw__no_bind();
get_error();
}
void pre_bind() {
this->vao->bind();
this->program->bind();
}
void draw_indexed_no_bind(GLenum mode, GLushort *indices, GLsizei count)
/// This method doesn't call prepare_draw/finalize_draw itself, it
/// must be handled by the caller. This is because this command
/// can be called several times on the same buffer (i.e. multiple
/// draw calls between the prepare/finalize)
void draw_indexed__raw(GLenum mode, GLushort *indices, GLsizei count)
{
printf("Draw indexed %d/%lu\n", count, this->map_index);
this->bind();
if (this->empty()) {
return;
}
struct Storage<T> *buffer = this->get_active_buffer();
GLint base = buffer->offset;
glDrawElementsBaseVertex(mode,
count,
GL_UNSIGNED_SHORT,
indices,
base);
glDrawElements(mode, count, GL_UNSIGNED_SHORT, indices);
get_error();
}
size_t remaining_capacity()
{
return this->capacity - this->active_next_index;
return this->capacity - this->map_index;
}
};

View File

@ -40,7 +40,7 @@ RetroGl::RetroGl(VideoClock video_clock)
{
retro_pixel_format f = RETRO_PIXEL_FORMAT_XRGB8888;
if ( !environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &f) ) {
puts("Can't set pixel format\n");
puts("Can't set pixel format");
exit(EXIT_FAILURE);
}
@ -56,7 +56,7 @@ RetroGl::RetroGl(VideoClock video_clock)
params.imm_vbo_disable = NULL;
if ( !glsm_ctl(GLSM_CTL_STATE_CONTEXT_INIT, &params) ) {
puts("Failed to init hardware context\n");
puts("Failed to init hardware context");
// TODO: Move this out to a init function to avoid exceptions?
throw std::runtime_error("Failed to init GLSM context.");
}
@ -88,7 +88,7 @@ RetroGl::~RetroGl() {
}
void RetroGl::context_reset() {
puts("OpenGL context reset\n");
puts("OpenGL context reset");
glsm_ctl(GLSM_CTL_STATE_CONTEXT_RESET, NULL);
if (!glsm_ctl(GLSM_CTL_STATE_SETUP, NULL))
@ -127,7 +127,7 @@ GlRenderer* RetroGl::gl_renderer()
case GlState_Valid:
return this->state_data.r;
default:
puts("Attempted to get GL state without GL context!\n");
puts("Attempted to get GL state without GL context!");
exit(EXIT_FAILURE);
}
}
@ -163,7 +163,7 @@ void RetroGl::prepare_render()
renderer = this->state_data.r;
break;
case GlState_Invalid:
puts("Attempted to render a frame without GL context\n");
puts("Attempted to render a frame without GL context");
exit(EXIT_FAILURE);
}
@ -179,7 +179,7 @@ void RetroGl::finalize_frame()
renderer = this->state_data.r;
break;
case GlState_Invalid:
puts("Attempted to render a frame without GL context\n");
puts("Attempted to render a frame without GL context");
exit(EXIT_FAILURE);
}
@ -229,8 +229,8 @@ void RetroGl::refresh_variables()
if (!ok)
{
puts("Couldn't change frontend resolution\n");
puts("Try resetting to enable the new configuration\n");
puts("Couldn't change frontend resolution");
puts("Try resetting to enable the new configuration");
}
}
}

View File

@ -30,7 +30,7 @@ public:
const GLvoid* gl_offset();
};
/*
/*
<simias> in order to create the vertex attrib array I need to know:
the GL type of the field, the number of components (unary, pair or triple)
and the offset within the struct