/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/ui/gl/gl_immediate_drawer.h" #include #include "xenia/base/assert.h" #include "xenia/ui/graphics_context.h" namespace xe { namespace ui { namespace gl { constexpr uint32_t kMaxDrawVertices = 64 * 1024; constexpr uint32_t kMaxDrawIndices = 64 * 1024; class GLImmediateTexture : public ImmediateTexture { public: GLImmediateTexture(uint32_t width, uint32_t height, ImmediateTextureFilter filter, bool repeat) : ImmediateTexture(width, height) { GLuint gl_handle; glCreateTextures(GL_TEXTURE_2D, 1, &gl_handle); GLenum gl_filter = GL_NEAREST; switch (filter) { case ImmediateTextureFilter::kNearest: gl_filter = GL_NEAREST; break; case ImmediateTextureFilter::kLinear: gl_filter = GL_LINEAR; break; } glTextureParameteri(gl_handle, GL_TEXTURE_MIN_FILTER, gl_filter); glTextureParameteri(gl_handle, GL_TEXTURE_MAG_FILTER, gl_filter); glTextureParameteri(gl_handle, GL_TEXTURE_WRAP_S, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE); glTextureParameteri(gl_handle, GL_TEXTURE_WRAP_T, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE); glTextureStorage2D(gl_handle, 1, GL_RGBA8, width, height); handle = static_cast(gl_handle); } ~GLImmediateTexture() override { GLuint gl_handle = static_cast(handle); glDeleteTextures(1, &gl_handle); } }; GLImmediateDrawer::GLImmediateDrawer(GraphicsContext* graphics_context) : ImmediateDrawer(graphics_context) { glCreateBuffers(1, &vertex_buffer_); glNamedBufferStorage(vertex_buffer_, kMaxDrawVertices * sizeof(ImmediateVertex), nullptr, GL_DYNAMIC_STORAGE_BIT); glCreateBuffers(1, &index_buffer_); glNamedBufferStorage(index_buffer_, kMaxDrawIndices * sizeof(uint16_t), nullptr, GL_DYNAMIC_STORAGE_BIT); glCreateVertexArrays(1, &vao_); glEnableVertexArrayAttrib(vao_, 0); glVertexArrayAttribBinding(vao_, 0, 0); glVertexArrayAttribFormat(vao_, 0, 2, GL_FLOAT, GL_FALSE, offsetof(ImmediateVertex, x)); glEnableVertexArrayAttrib(vao_, 1); glVertexArrayAttribBinding(vao_, 1, 0); glVertexArrayAttribFormat(vao_, 1, 2, GL_FLOAT, GL_FALSE, offsetof(ImmediateVertex, u)); glEnableVertexArrayAttrib(vao_, 2); glVertexArrayAttribBinding(vao_, 2, 0); glVertexArrayAttribFormat(vao_, 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, offsetof(ImmediateVertex, color)); glVertexArrayVertexBuffer(vao_, 0, vertex_buffer_, 0, sizeof(ImmediateVertex)); InitializeShaders(); } GLImmediateDrawer::~GLImmediateDrawer() { GraphicsContextLock lock(graphics_context_); glDeleteBuffers(1, &vertex_buffer_); glDeleteBuffers(1, &index_buffer_); glDeleteVertexArrays(1, &vao_); glDeleteProgram(program_); } void GLImmediateDrawer::InitializeShaders() { const std::string header = R"( #version 450 #extension GL_ARB_explicit_uniform_location : require #extension GL_ARB_shading_language_420pack : require precision highp float; layout(std140, column_major) uniform; layout(std430, column_major) buffer; )"; const std::string vertex_shader_source = header + R"( layout(location = 0) uniform mat4 projection_matrix; layout(location = 0) in vec2 in_pos; layout(location = 1) in vec2 in_uv; layout(location = 2) in vec4 in_color; layout(location = 0) out vec2 vtx_uv; layout(location = 1) out vec4 vtx_color; void main() { gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0); vtx_uv = in_uv; vtx_color = in_color; })"; const std::string fragment_shader_source = header + R"( layout(location = 1) uniform sampler2D texture_sampler; layout(location = 2) uniform int restrict_texture_samples; layout(location = 0) in vec2 vtx_uv; layout(location = 1) in vec4 vtx_color; layout(location = 0) out vec4 out_color; void main() { out_color = vtx_color; if (restrict_texture_samples == 0 || vtx_uv.x <= 1.0) { vec4 tex_color = texture(texture_sampler, vtx_uv); out_color *= tex_color; // TODO(benvanik): microprofiler shadows. } })"; GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); const char* vertex_shader_source_ptr = vertex_shader_source.c_str(); GLint vertex_shader_source_length = GLint(vertex_shader_source.size()); glShaderSource(vertex_shader, 1, &vertex_shader_source_ptr, &vertex_shader_source_length); glCompileShader(vertex_shader); GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); const char* fragment_shader_source_ptr = fragment_shader_source.c_str(); GLint fragment_shader_source_length = GLint(fragment_shader_source.size()); glShaderSource(fragment_shader, 1, &fragment_shader_source_ptr, &fragment_shader_source_length); glCompileShader(fragment_shader); program_ = glCreateProgram(); glAttachShader(program_, vertex_shader); glAttachShader(program_, fragment_shader); glLinkProgram(program_); glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); } std::unique_ptr GLImmediateDrawer::CreateTexture( uint32_t width, uint32_t height, ImmediateTextureFilter filter, bool repeat, const uint8_t* data) { GraphicsContextLock lock(graphics_context_); auto texture = std::make_unique(width, height, filter, repeat); if (data) { UpdateTexture(texture.get(), data); } return std::unique_ptr(texture.release()); } void GLImmediateDrawer::UpdateTexture(ImmediateTexture* texture, const uint8_t* data) { GraphicsContextLock lock(graphics_context_); glTextureSubImage2D(static_cast(texture->handle), 0, 0, 0, texture->width, texture->height, GL_RGBA, GL_UNSIGNED_BYTE, data); } void GLImmediateDrawer::Begin(int render_target_width, int render_target_height) { was_current_ = graphics_context_->is_current(); if (!was_current_) { graphics_context_->MakeCurrent(); } // Setup render state. glEnablei(GL_BLEND, 0); glBlendEquationi(0, GL_FUNC_ADD); glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); // Prepare drawing resources. glUseProgram(program_); glBindVertexArray(vao_); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_); // Setup orthographic projection matrix and viewport. const float ortho_projection[4][4] = { {2.0f / render_target_width, 0.0f, 0.0f, 0.0f}, {0.0f, 2.0f / -render_target_height, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f, 1.0f}, }; glProgramUniformMatrix4fv(program_, 0, 1, GL_FALSE, &ortho_projection[0][0]); glViewport(0, 0, render_target_width, render_target_height); } void GLImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) { assert_true(batch.vertex_count <= kMaxDrawVertices); glNamedBufferSubData(vertex_buffer_, 0, batch.vertex_count * sizeof(ImmediateVertex), batch.vertices); if (batch.indices) { assert_true(batch.index_count <= kMaxDrawIndices); glNamedBufferSubData(index_buffer_, 0, batch.index_count * sizeof(uint16_t), batch.indices); } batch_has_index_buffer_ = !!batch.indices; } void GLImmediateDrawer::Draw(const ImmediateDraw& draw) { if (draw.scissor) { glEnable(GL_SCISSOR_TEST); glScissorIndexed(0, draw.scissor_rect[0], draw.scissor_rect[1], draw.scissor_rect[2], draw.scissor_rect[3]); } else { glDisable(GL_SCISSOR_TEST); } if (draw.alpha_blend) { glEnablei(GL_BLEND, 0); glBlendEquationi(0, GL_FUNC_ADD); glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisablei(GL_BLEND, 0); } if (draw.texture_handle) { glBindTextureUnit(0, static_cast(draw.texture_handle)); } else { glBindTextureUnit(0, 0); } glProgramUniform1i(program_, 2, draw.restrict_texture_samples ? 1 : 0); GLenum mode = GL_TRIANGLES; switch (draw.primitive_type) { case ImmediatePrimitiveType::kLines: mode = GL_LINES; break; case ImmediatePrimitiveType::kTriangles: mode = GL_TRIANGLES; break; } if (batch_has_index_buffer_) { glDrawElementsBaseVertex( mode, draw.count, GL_UNSIGNED_SHORT, reinterpret_cast(draw.index_offset * sizeof(uint16_t)), draw.base_vertex); } else { glDrawArrays(mode, draw.base_vertex, draw.count); } } void GLImmediateDrawer::EndDrawBatch() { glFlush(); } void GLImmediateDrawer::End() { // Restore modified state. glDisable(GL_SCISSOR_TEST); glBindTextureUnit(0, 0); glUseProgram(0); glBindVertexArray(0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); if (!was_current_) { graphics_context_->ClearCurrent(); } } } // namespace gl } // namespace ui } // namespace xe