2022-02-16 15:34:42 +01:00
|
|
|
/* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
* Copyright 2011-2022 Blender Foundation */
|
2021-09-17 15:15:14 +02:00
|
|
|
|
|
|
|
|
#include "app/opengl/display_driver.h"
|
|
|
|
|
#include "app/opengl/shader.h"
|
|
|
|
|
|
|
|
|
|
#include "util/log.h"
|
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
|
|
#include <SDL.h>
|
2022-08-15 14:58:04 +02:00
|
|
|
#include <epoxy/gl.h>
|
2021-09-17 15:15:14 +02:00
|
|
|
|
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
|
* OpenGLDisplayDriver.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
OpenGLDisplayDriver::OpenGLDisplayDriver(const function<bool()> &gl_context_enable,
|
|
|
|
|
const function<void()> &gl_context_disable)
|
|
|
|
|
: gl_context_enable_(gl_context_enable), gl_context_disable_(gl_context_disable)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OpenGLDisplayDriver::~OpenGLDisplayDriver()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
|
* Update procedure.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::next_tile_begin()
|
|
|
|
|
{
|
|
|
|
|
/* Assuming no tiles used in interactive display. */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenGLDisplayDriver::update_begin(const Params ¶ms, int texture_width, int texture_height)
|
|
|
|
|
{
|
|
|
|
|
/* Note that it's the responsibility of OpenGLDisplayDriver to ensure updating and drawing
|
|
|
|
|
* the texture does not happen at the same time. This is achieved indirectly.
|
|
|
|
|
*
|
|
|
|
|
* When enabling the OpenGL context, it uses an internal mutex lock DST.gl_context_lock.
|
|
|
|
|
* This same lock is also held when do_draw() is called, which together ensure mutual
|
|
|
|
|
* exclusion.
|
|
|
|
|
*
|
|
|
|
|
* This locking is not performed on the Cycles side, because that would cause lock inversion. */
|
|
|
|
|
if (!gl_context_enable_()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gl_render_sync_) {
|
|
|
|
|
glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!gl_texture_resources_ensure()) {
|
|
|
|
|
gl_context_disable_();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Update texture dimensions if needed. */
|
|
|
|
|
if (texture_.width != texture_width || texture_.height != texture_height) {
|
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture_.gl_id);
|
|
|
|
|
glTexImage2D(
|
|
|
|
|
GL_TEXTURE_2D, 0, GL_RGBA16F, texture_width, texture_height, 0, GL_RGBA, GL_HALF_FLOAT, 0);
|
|
|
|
|
texture_.width = texture_width;
|
|
|
|
|
texture_.height = texture_height;
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
|
|
/* Texture did change, and no pixel storage was provided. Tag for an explicit zeroing out to
|
|
|
|
|
* avoid undefined content. */
|
|
|
|
|
texture_.need_clear = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Update PBO dimensions if needed.
|
|
|
|
|
*
|
2022-02-23 18:24:08 +11:00
|
|
|
* NOTE: Allocate the PBO for the size which will fit the final render resolution (as in,
|
2021-09-17 15:15:14 +02:00
|
|
|
* at a resolution divider 1. This was we don't need to recreate graphics interoperability
|
|
|
|
|
* objects which are costly and which are tied to the specific underlying buffer size.
|
|
|
|
|
* The downside of this approach is that when graphics interoperability is not used we are
|
|
|
|
|
* sending too much data to GPU when resolution divider is not 1. */
|
|
|
|
|
const int buffer_width = params.full_size.x;
|
|
|
|
|
const int buffer_height = params.full_size.y;
|
|
|
|
|
if (texture_.buffer_width != buffer_width || texture_.buffer_height != buffer_height) {
|
|
|
|
|
const size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height;
|
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id);
|
|
|
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER, size_in_bytes, 0, GL_DYNAMIC_DRAW);
|
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
|
|
|
|
|
|
texture_.buffer_width = buffer_width;
|
|
|
|
|
texture_.buffer_height = buffer_height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* New content will be provided to the texture in one way or another, so mark this in a
|
|
|
|
|
* centralized place. */
|
|
|
|
|
texture_.need_update = true;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::update_end()
|
|
|
|
|
{
|
|
|
|
|
gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
|
glFlush();
|
|
|
|
|
|
|
|
|
|
gl_context_disable_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
|
* Texture buffer mapping.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
half4 *OpenGLDisplayDriver::map_texture_buffer()
|
|
|
|
|
{
|
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id);
|
|
|
|
|
|
|
|
|
|
half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(
|
|
|
|
|
glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
|
|
|
|
|
if (!mapped_rgba_pixels) {
|
|
|
|
|
LOG(ERROR) << "Error mapping OpenGLDisplayDriver pixel buffer object.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (texture_.need_clear) {
|
|
|
|
|
const int64_t texture_width = texture_.width;
|
|
|
|
|
const int64_t texture_height = texture_.height;
|
|
|
|
|
memset(reinterpret_cast<void *>(mapped_rgba_pixels),
|
|
|
|
|
0,
|
|
|
|
|
texture_width * texture_height * sizeof(half4));
|
|
|
|
|
texture_.need_clear = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mapped_rgba_pixels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::unmap_texture_buffer()
|
|
|
|
|
{
|
|
|
|
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
|
|
|
|
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
|
* Graphics interoperability.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
OpenGLDisplayDriver::GraphicsInterop OpenGLDisplayDriver::graphics_interop_get()
|
|
|
|
|
{
|
|
|
|
|
GraphicsInterop interop_dst;
|
|
|
|
|
|
|
|
|
|
interop_dst.buffer_width = texture_.buffer_width;
|
|
|
|
|
interop_dst.buffer_height = texture_.buffer_height;
|
|
|
|
|
interop_dst.opengl_pbo_id = texture_.gl_pbo_id;
|
|
|
|
|
|
|
|
|
|
interop_dst.need_clear = texture_.need_clear;
|
|
|
|
|
texture_.need_clear = false;
|
|
|
|
|
|
|
|
|
|
return interop_dst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::graphics_interop_activate()
|
|
|
|
|
{
|
|
|
|
|
gl_context_enable_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::graphics_interop_deactivate()
|
|
|
|
|
{
|
|
|
|
|
gl_context_disable_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
|
* Drawing.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::clear()
|
|
|
|
|
{
|
|
|
|
|
texture_.need_clear = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::draw(const Params ¶ms)
|
|
|
|
|
{
|
|
|
|
|
/* See do_update_begin() for why no locking is required here. */
|
|
|
|
|
if (texture_.need_clear) {
|
|
|
|
|
/* Texture is requested to be cleared and was not yet cleared.
|
|
|
|
|
* Do early return which should be equivalent of drawing all-zero texture. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!gl_draw_resources_ensure()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gl_upload_sync_) {
|
|
|
|
|
glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glEnable(GL_BLEND);
|
|
|
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
|
|
|
|
|
|
display_shader_.bind(params.full_size.x, params.full_size.y);
|
|
|
|
|
|
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture_.gl_id);
|
|
|
|
|
|
|
|
|
|
if (texture_.width != params.size.x || texture_.height != params.size.y) {
|
|
|
|
|
/* Resolution divider is different from 1, force nearest interpolation. */
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
|
|
|
|
|
|
|
|
|
|
texture_update_if_needed();
|
|
|
|
|
vertex_buffer_update(params);
|
|
|
|
|
|
|
|
|
|
GLuint vertex_array_object;
|
|
|
|
|
glGenVertexArrays(1, &vertex_array_object);
|
|
|
|
|
glBindVertexArray(vertex_array_object);
|
|
|
|
|
|
|
|
|
|
const int texcoord_attribute = display_shader_.get_tex_coord_attrib_location();
|
|
|
|
|
const int position_attribute = display_shader_.get_position_attrib_location();
|
|
|
|
|
|
|
|
|
|
glEnableVertexAttribArray(texcoord_attribute);
|
|
|
|
|
glEnableVertexAttribArray(position_attribute);
|
|
|
|
|
|
|
|
|
|
glVertexAttribPointer(
|
|
|
|
|
texcoord_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)0);
|
|
|
|
|
glVertexAttribPointer(position_attribute,
|
|
|
|
|
2,
|
|
|
|
|
GL_FLOAT,
|
|
|
|
|
GL_FALSE,
|
|
|
|
|
4 * sizeof(float),
|
|
|
|
|
(const GLvoid *)(sizeof(float) * 2));
|
|
|
|
|
|
|
|
|
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
|
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
|
|
glDeleteVertexArrays(1, &vertex_array_object);
|
|
|
|
|
|
|
|
|
|
display_shader_.unbind();
|
|
|
|
|
|
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
|
|
|
|
|
|
gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
|
glFlush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenGLDisplayDriver::gl_draw_resources_ensure()
|
|
|
|
|
{
|
|
|
|
|
if (!texture_.gl_id) {
|
|
|
|
|
/* If there is no texture allocated, there is nothing to draw. Inform the draw call that it can
|
|
|
|
|
* can not continue. Note that this is not an unrecoverable error, so once the texture is known
|
|
|
|
|
* we will come back here and create all the GPU resources needed for draw. */
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gl_draw_resource_creation_attempted_) {
|
|
|
|
|
return gl_draw_resources_created_;
|
|
|
|
|
}
|
|
|
|
|
gl_draw_resource_creation_attempted_ = true;
|
|
|
|
|
|
|
|
|
|
if (!vertex_buffer_) {
|
|
|
|
|
glGenBuffers(1, &vertex_buffer_);
|
|
|
|
|
if (!vertex_buffer_) {
|
|
|
|
|
LOG(ERROR) << "Error creating vertex buffer.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gl_draw_resources_created_ = true;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::gl_resources_destroy()
|
|
|
|
|
{
|
|
|
|
|
gl_context_enable_();
|
|
|
|
|
|
|
|
|
|
if (vertex_buffer_ != 0) {
|
|
|
|
|
glDeleteBuffers(1, &vertex_buffer_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (texture_.gl_pbo_id) {
|
|
|
|
|
glDeleteBuffers(1, &texture_.gl_pbo_id);
|
|
|
|
|
texture_.gl_pbo_id = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (texture_.gl_id) {
|
|
|
|
|
glDeleteTextures(1, &texture_.gl_id);
|
|
|
|
|
texture_.gl_id = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gl_context_disable_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OpenGLDisplayDriver::gl_texture_resources_ensure()
|
|
|
|
|
{
|
|
|
|
|
if (texture_.creation_attempted) {
|
|
|
|
|
return texture_.is_created;
|
|
|
|
|
}
|
|
|
|
|
texture_.creation_attempted = true;
|
|
|
|
|
|
|
|
|
|
DCHECK(!texture_.gl_id);
|
|
|
|
|
DCHECK(!texture_.gl_pbo_id);
|
|
|
|
|
|
|
|
|
|
/* Create texture. */
|
|
|
|
|
glGenTextures(1, &texture_.gl_id);
|
|
|
|
|
if (!texture_.gl_id) {
|
|
|
|
|
LOG(ERROR) << "Error creating texture.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Configure the texture. */
|
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture_.gl_id);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
|
|
/* Create PBO for the texture. */
|
|
|
|
|
glGenBuffers(1, &texture_.gl_pbo_id);
|
|
|
|
|
if (!texture_.gl_pbo_id) {
|
|
|
|
|
LOG(ERROR) << "Error creating texture pixel buffer object.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Creation finished with a success. */
|
|
|
|
|
texture_.is_created = true;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::texture_update_if_needed()
|
|
|
|
|
{
|
|
|
|
|
if (!texture_.need_update) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id);
|
|
|
|
|
glTexSubImage2D(
|
|
|
|
|
GL_TEXTURE_2D, 0, 0, 0, texture_.width, texture_.height, GL_RGBA, GL_HALF_FLOAT, 0);
|
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
|
|
|
|
|
|
texture_.need_update = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OpenGLDisplayDriver::vertex_buffer_update(const Params ¶ms)
|
|
|
|
|
{
|
|
|
|
|
/* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be
|
|
|
|
|
* rendered. */
|
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), NULL, GL_STREAM_DRAW);
|
|
|
|
|
|
|
|
|
|
float *vpointer = reinterpret_cast<float *>(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY));
|
|
|
|
|
if (!vpointer) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vpointer[0] = 0.0f;
|
|
|
|
|
vpointer[1] = 0.0f;
|
|
|
|
|
vpointer[2] = params.full_offset.x;
|
|
|
|
|
vpointer[3] = params.full_offset.y;
|
|
|
|
|
|
|
|
|
|
vpointer[4] = 1.0f;
|
|
|
|
|
vpointer[5] = 0.0f;
|
|
|
|
|
vpointer[6] = (float)params.size.x + params.full_offset.x;
|
|
|
|
|
vpointer[7] = params.full_offset.y;
|
|
|
|
|
|
|
|
|
|
vpointer[8] = 1.0f;
|
|
|
|
|
vpointer[9] = 1.0f;
|
|
|
|
|
vpointer[10] = (float)params.size.x + params.full_offset.x;
|
|
|
|
|
vpointer[11] = (float)params.size.y + params.full_offset.y;
|
|
|
|
|
|
|
|
|
|
vpointer[12] = 0.0f;
|
|
|
|
|
vpointer[13] = 1.0f;
|
|
|
|
|
vpointer[14] = params.full_offset.x;
|
|
|
|
|
vpointer[15] = (float)params.size.y + params.full_offset.y;
|
|
|
|
|
|
|
|
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CCL_NAMESPACE_END
|