Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/cycles/app/opengl')
-rw-r--r--intern/cycles/app/opengl/display_driver.cpp385
-rw-r--r--intern/cycles/app/opengl/display_driver.h117
-rw-r--r--intern/cycles/app/opengl/shader.cpp197
-rw-r--r--intern/cycles/app/opengl/shader.h45
-rw-r--r--intern/cycles/app/opengl/window.cpp352
-rw-r--r--intern/cycles/app/opengl/window.h35
6 files changed, 1131 insertions, 0 deletions
diff --git a/intern/cycles/app/opengl/display_driver.cpp b/intern/cycles/app/opengl/display_driver.cpp
new file mode 100644
index 00000000000..6e610b8b02c
--- /dev/null
+++ b/intern/cycles/app/opengl/display_driver.cpp
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#include "app/opengl/display_driver.h"
+#include "app/opengl/shader.h"
+
+#include "util/log.h"
+#include "util/string.h"
+
+#include <GL/glew.h>
+#include <SDL.h>
+
+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 &params, 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.
+ *
+ * NOTE: Allocate the PBO for the the size which will fit the final render resolution (as in,
+ * 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 &params)
+{
+ /* 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 &params)
+{
+ /* 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
diff --git a/intern/cycles/app/opengl/display_driver.h b/intern/cycles/app/opengl/display_driver.h
new file mode 100644
index 00000000000..92578412d68
--- /dev/null
+++ b/intern/cycles/app/opengl/display_driver.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#pragma once
+
+#include <atomic>
+
+#include "app/opengl/shader.h"
+
+#include "session/display_driver.h"
+
+#include "util/function.h"
+#include "util/unique_ptr.h"
+
+CCL_NAMESPACE_BEGIN
+
+class OpenGLDisplayDriver : public DisplayDriver {
+ public:
+ /* Callbacks for enabling and disabling the OpenGL context. Must be provided to support enabling
+ * the context on the Cycles render thread independent of the main thread. */
+ OpenGLDisplayDriver(const function<bool()> &gl_context_enable,
+ const function<void()> &gl_context_disable);
+ ~OpenGLDisplayDriver();
+
+ virtual void graphics_interop_activate() override;
+ virtual void graphics_interop_deactivate() override;
+
+ virtual void clear() override;
+
+ void set_zoom(float zoom_x, float zoom_y);
+
+ protected:
+ virtual void next_tile_begin() override;
+
+ virtual bool update_begin(const Params &params, int texture_width, int texture_height) override;
+ virtual void update_end() override;
+
+ virtual half4 *map_texture_buffer() override;
+ virtual void unmap_texture_buffer() override;
+
+ virtual GraphicsInterop graphics_interop_get() override;
+
+ virtual void draw(const Params &params) override;
+
+ /* Make sure texture is allocated and its initial configuration is performed. */
+ bool gl_texture_resources_ensure();
+
+ /* Ensure all runtime GPU resources needed for drawing are allocated.
+ * Returns true if all resources needed for drawing are available. */
+ bool gl_draw_resources_ensure();
+
+ /* Destroy all GPU resources which are being used by this object. */
+ void gl_resources_destroy();
+
+ /* Update GPU texture dimensions and content if needed (new pixel data was provided).
+ *
+ * NOTE: The texture needs to be bound. */
+ void texture_update_if_needed();
+
+ /* Update vertex buffer with new coordinates of vertex positions and texture coordinates.
+ * This buffer is used to render texture in the viewport.
+ *
+ * NOTE: The buffer needs to be bound. */
+ void vertex_buffer_update(const Params &params);
+
+ /* Texture which contains pixels of the render result. */
+ struct {
+ /* Indicates whether texture creation was attempted and succeeded.
+ * Used to avoid multiple attempts of texture creation on GPU issues or GPU context
+ * misconfiguration. */
+ bool creation_attempted = false;
+ bool is_created = false;
+
+ /* OpenGL resource IDs of the texture itself and Pixel Buffer Object (PBO) used to write
+ * pixels to it.
+ *
+ * NOTE: Allocated on the engine's context. */
+ uint gl_id = 0;
+ uint gl_pbo_id = 0;
+
+ /* Is true when new data was written to the PBO, meaning, the texture might need to be resized
+ * and new data is to be uploaded to the GPU. */
+ bool need_update = false;
+
+ /* Content of the texture is to be filled with zeroes. */
+ std::atomic<bool> need_clear = true;
+
+ /* Dimensions of the texture in pixels. */
+ int width = 0;
+ int height = 0;
+
+ /* Dimensions of the underlying PBO. */
+ int buffer_width = 0;
+ int buffer_height = 0;
+ } texture_;
+
+ OpenGLShader display_shader_;
+
+ /* Special track of whether GPU resources were attempted to be created, to avoid attempts of
+ * their re-creation on failure on every redraw. */
+ bool gl_draw_resource_creation_attempted_ = false;
+ bool gl_draw_resources_created_ = false;
+
+ /* Vertex buffer which hold vertices of a triangle fan which is textures with the texture
+ * holding the render result. */
+ uint vertex_buffer_ = 0;
+
+ void *gl_render_sync_ = nullptr;
+ void *gl_upload_sync_ = nullptr;
+
+ float2 zoom_ = make_float2(1.0f, 1.0f);
+
+ function<bool()> gl_context_enable_ = nullptr;
+ function<void()> gl_context_disable_ = nullptr;
+};
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/app/opengl/shader.cpp b/intern/cycles/app/opengl/shader.cpp
new file mode 100644
index 00000000000..9db9ea7fce9
--- /dev/null
+++ b/intern/cycles/app/opengl/shader.cpp
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#include "app/opengl/shader.h"
+
+#include "util/log.h"
+#include "util/string.h"
+
+#include <GL/glew.h>
+
+CCL_NAMESPACE_BEGIN
+
+/* --------------------------------------------------------------------
+ * OpenGLShader.
+ */
+
+static const char *VERTEX_SHADER =
+ "#version 330\n"
+ "uniform vec2 fullscreen;\n"
+ "in vec2 texCoord;\n"
+ "in vec2 pos;\n"
+ "out vec2 texCoord_interp;\n"
+ "\n"
+ "vec2 normalize_coordinates()\n"
+ "{\n"
+ " return (vec2(2.0) * (pos / fullscreen)) - vec2(1.0);\n"
+ "}\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = vec4(normalize_coordinates(), 0.0, 1.0);\n"
+ " texCoord_interp = texCoord;\n"
+ "}\n\0";
+
+static const char *FRAGMENT_SHADER =
+ "#version 330\n"
+ "uniform sampler2D image_texture;\n"
+ "in vec2 texCoord_interp;\n"
+ "out vec4 fragColor;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " vec4 rgba = texture(image_texture, texCoord_interp);\n"
+ /* Harcoded Rec.709 gamma, should use OpenColorIO eventually. */
+ " fragColor = pow(rgba, vec4(0.45, 0.45, 0.45, 1.0));\n"
+ "}\n\0";
+
+static void shader_print_errors(const char *task, const char *log, const char *code)
+{
+ LOG(ERROR) << "Shader: " << task << " error:";
+ LOG(ERROR) << "===== shader string ====";
+
+ stringstream stream(code);
+ string partial;
+
+ int line = 1;
+ while (getline(stream, partial, '\n')) {
+ if (line < 10) {
+ LOG(ERROR) << " " << line << " " << partial;
+ }
+ else {
+ LOG(ERROR) << line << " " << partial;
+ }
+ line++;
+ }
+ LOG(ERROR) << log;
+}
+
+static int compile_shader_program(void)
+{
+ const struct Shader {
+ const char *source;
+ const GLenum type;
+ } shaders[2] = {{VERTEX_SHADER, GL_VERTEX_SHADER}, {FRAGMENT_SHADER, GL_FRAGMENT_SHADER}};
+
+ const GLuint program = glCreateProgram();
+
+ for (int i = 0; i < 2; i++) {
+ const GLuint shader = glCreateShader(shaders[i].type);
+
+ string source_str = shaders[i].source;
+ const char *c_str = source_str.c_str();
+
+ glShaderSource(shader, 1, &c_str, NULL);
+ glCompileShader(shader);
+
+ GLint compile_status;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
+
+ if (!compile_status) {
+ GLchar log[5000];
+ GLsizei length = 0;
+ glGetShaderInfoLog(shader, sizeof(log), &length, log);
+ shader_print_errors("compile", log, c_str);
+ return 0;
+ }
+
+ glAttachShader(program, shader);
+ }
+
+ /* Link output. */
+ glBindFragDataLocation(program, 0, "fragColor");
+
+ /* Link and error check. */
+ glLinkProgram(program);
+
+ GLint link_status;
+ glGetProgramiv(program, GL_LINK_STATUS, &link_status);
+ if (!link_status) {
+ GLchar log[5000];
+ GLsizei length = 0;
+ glGetShaderInfoLog(program, sizeof(log), &length, log);
+ shader_print_errors("linking", log, VERTEX_SHADER);
+ shader_print_errors("linking", log, FRAGMENT_SHADER);
+ return 0;
+ }
+
+ return program;
+}
+
+int OpenGLShader::get_position_attrib_location()
+{
+ if (position_attribute_location_ == -1) {
+ const uint shader_program = get_shader_program();
+ position_attribute_location_ = glGetAttribLocation(shader_program, position_attribute_name);
+ }
+ return position_attribute_location_;
+}
+
+int OpenGLShader::get_tex_coord_attrib_location()
+{
+ if (tex_coord_attribute_location_ == -1) {
+ const uint shader_program = get_shader_program();
+ tex_coord_attribute_location_ = glGetAttribLocation(shader_program, tex_coord_attribute_name);
+ }
+ return tex_coord_attribute_location_;
+}
+
+void OpenGLShader::bind(int width, int height)
+{
+ create_shader_if_needed();
+
+ if (!shader_program_) {
+ return;
+ }
+
+ glUseProgram(shader_program_);
+ glUniform1i(image_texture_location_, 0);
+ glUniform2f(fullscreen_location_, width, height);
+}
+
+void OpenGLShader::unbind()
+{
+}
+
+uint OpenGLShader::get_shader_program()
+{
+ return shader_program_;
+}
+
+void OpenGLShader::create_shader_if_needed()
+{
+ if (shader_program_ || shader_compile_attempted_) {
+ return;
+ }
+
+ shader_compile_attempted_ = true;
+
+ shader_program_ = compile_shader_program();
+ if (!shader_program_) {
+ return;
+ }
+
+ glUseProgram(shader_program_);
+
+ image_texture_location_ = glGetUniformLocation(shader_program_, "image_texture");
+ if (image_texture_location_ < 0) {
+ LOG(ERROR) << "Shader doesn't contain the 'image_texture' uniform.";
+ destroy_shader();
+ return;
+ }
+
+ fullscreen_location_ = glGetUniformLocation(shader_program_, "fullscreen");
+ if (fullscreen_location_ < 0) {
+ LOG(ERROR) << "Shader doesn't contain the 'fullscreen' uniform.";
+ destroy_shader();
+ return;
+ }
+}
+
+void OpenGLShader::destroy_shader()
+{
+ glDeleteProgram(shader_program_);
+ shader_program_ = 0;
+}
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/app/opengl/shader.h b/intern/cycles/app/opengl/shader.h
new file mode 100644
index 00000000000..6ca121ca6ff
--- /dev/null
+++ b/intern/cycles/app/opengl/shader.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 OpenGL Foundation */
+
+#pragma once
+
+#include "util/types.h"
+
+CCL_NAMESPACE_BEGIN
+
+class OpenGLShader {
+ public:
+ static constexpr const char *position_attribute_name = "pos";
+ static constexpr const char *tex_coord_attribute_name = "texCoord";
+
+ OpenGLShader() = default;
+ virtual ~OpenGLShader() = default;
+
+ /* Get attribute location for position and texture coordinate respectively.
+ * NOTE: The shader needs to be bound to have access to those. */
+ int get_position_attrib_location();
+ int get_tex_coord_attrib_location();
+
+ void bind(int width, int height);
+ void unbind();
+
+ protected:
+ uint get_shader_program();
+
+ void create_shader_if_needed();
+ void destroy_shader();
+
+ /* Cached values of various OpenGL resources. */
+ int position_attribute_location_ = -1;
+ int tex_coord_attribute_location_ = -1;
+
+ uint shader_program_ = 0;
+ int image_texture_location_ = -1;
+ int fullscreen_location_ = -1;
+
+ /* Shader compilation attempted. Which means, that if the shader program is 0 then compilation or
+ * linking has failed. Do not attempt to re-compile the shader. */
+ bool shader_compile_attempted_ = false;
+};
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/app/opengl/window.cpp b/intern/cycles/app/opengl/window.cpp
new file mode 100644
index 00000000000..7351ae3eecd
--- /dev/null
+++ b/intern/cycles/app/opengl/window.cpp
@@ -0,0 +1,352 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "app/opengl/window.h"
+
+#include "util/string.h"
+#include "util/thread.h"
+#include "util/time.h"
+#include "util/version.h"
+
+#include <GL/glew.h>
+#include <SDL.h>
+
+CCL_NAMESPACE_BEGIN
+
+/* structs */
+
+struct Window {
+ WindowInitFunc initf = nullptr;
+ WindowExitFunc exitf = nullptr;
+ WindowResizeFunc resize = nullptr;
+ WindowDisplayFunc display = nullptr;
+ WindowKeyboardFunc keyboard = nullptr;
+ WindowMotionFunc motion = nullptr;
+
+ bool first_display = true;
+ bool redraw = false;
+
+ int mouseX = 0, mouseY = 0;
+ int mouseBut0 = 0, mouseBut2 = 0;
+
+ int width = 0, height = 0;
+
+ SDL_Window *window = nullptr;
+ SDL_GLContext gl_context = nullptr;
+ thread_mutex gl_context_mutex;
+} V;
+
+/* public */
+
+static void window_display_text(int x, int y, const char *text)
+{
+/* Not currently supported, need to add text rendering support. */
+#if 0
+ const char *c;
+
+ glRasterPos3f(x, y, 0);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ printf("display %s\n", text);
+
+ for (c = text; *c != '\0'; c++) {
+ const uint8_t *bitmap = helvetica10_character_map[*c];
+ glBitmap(bitmap[0],
+ helvetica10_height,
+ helvetica10_x_offset,
+ helvetica10_y_offset,
+ bitmap[0],
+ 0.0f,
+ bitmap + 1);
+ }
+#else
+ static string last_text = "";
+
+ if (text != last_text) {
+ printf("%s\n", text);
+ last_text = text;
+ }
+#endif
+}
+
+void window_display_info(const char *info)
+{
+ const int height = 20;
+
+#if 0
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.1f, 0.1f, 0.1f, 0.8f);
+ glRectf(0.0f, V.height - height, V.width, V.height);
+ glDisable(GL_BLEND);
+
+ glColor3f(0.5f, 0.5f, 0.5f);
+#endif
+
+ window_display_text(10, 7 + V.height - height, info);
+
+#if 0
+ glColor3f(1.0f, 1.0f, 1.0f);
+#endif
+}
+
+void window_display_help()
+{
+ const int w = (int)((float)V.width / 1.15f);
+ const int h = (int)((float)V.height / 1.15f);
+
+ const int x1 = (V.width - w) / 2;
+#if 0
+ const int x2 = x1 + w;
+#endif
+
+ const int y1 = (V.height - h) / 2;
+ const int y2 = y1 + h;
+
+#if 0
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.5f, 0.5f, 0.5f, 0.8f);
+ glRectf(x1, y1, x2, y2);
+ glDisable(GL_BLEND);
+
+ glColor3f(0.8f, 0.8f, 0.8f);
+#endif
+
+ string info = string("Cycles Renderer ") + CYCLES_VERSION_STRING;
+
+ window_display_text(x1 + 20, y2 - 20, info.c_str());
+ window_display_text(x1 + 20, y2 - 40, "(C) 2011-2016 Blender Foundation");
+ window_display_text(x1 + 20, y2 - 80, "Controls:");
+ window_display_text(x1 + 20, y2 - 100, "h: Info/Help");
+ window_display_text(x1 + 20, y2 - 120, "r: Reset");
+ window_display_text(x1 + 20, y2 - 140, "p: Pause");
+ window_display_text(x1 + 20, y2 - 160, "esc: Cancel");
+ window_display_text(x1 + 20, y2 - 180, "q: Quit program");
+
+ window_display_text(x1 + 20, y2 - 210, "i: Interactive mode");
+ window_display_text(x1 + 20, y2 - 230, "Left mouse: Move camera");
+ window_display_text(x1 + 20, y2 - 250, "Right mouse: Rotate camera");
+ window_display_text(x1 + 20, y2 - 270, "W/A/S/D: Move camera");
+ window_display_text(x1 + 20, y2 - 290, "0/1/2/3: Set max bounces");
+
+#if 0
+ glColor3f(1.0f, 1.0f, 1.0f);
+#endif
+}
+
+static void window_display()
+{
+ if (V.first_display) {
+ if (V.initf) {
+ V.initf();
+ }
+ if (V.exitf) {
+ atexit(V.exitf);
+ }
+
+ V.first_display = false;
+ }
+
+ window_opengl_context_enable();
+
+ glViewport(0, 0, V.width, V.height);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ glClearColor(0.05f, 0.05f, 0.05f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, V.width, 0, V.height, -1, 1);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ glRasterPos3f(0, 0, 0);
+
+ if (V.display)
+ V.display();
+
+ SDL_GL_SwapWindow(V.window);
+ window_opengl_context_disable();
+}
+
+static void window_reshape(int width, int height)
+{
+ if (V.width != width || V.height != height) {
+ if (V.resize) {
+ V.resize(width, height);
+ }
+ }
+
+ V.width = width;
+ V.height = height;
+}
+
+static bool window_keyboard(unsigned char key)
+{
+ if (V.keyboard)
+ V.keyboard(key);
+
+ if (key == 'q') {
+ if (V.exitf)
+ V.exitf();
+ return true;
+ }
+
+ return false;
+}
+
+static void window_mouse(int button, int state, int x, int y)
+{
+ if (button == SDL_BUTTON_LEFT) {
+ if (state == SDL_MOUSEBUTTONDOWN) {
+ V.mouseX = x;
+ V.mouseY = y;
+ V.mouseBut0 = 1;
+ }
+ else if (state == SDL_MOUSEBUTTONUP) {
+ V.mouseBut0 = 0;
+ }
+ }
+ else if (button == SDL_BUTTON_RIGHT) {
+ if (state == SDL_MOUSEBUTTONDOWN) {
+ V.mouseX = x;
+ V.mouseY = y;
+ V.mouseBut2 = 1;
+ }
+ else if (state == SDL_MOUSEBUTTONUP) {
+ V.mouseBut2 = 0;
+ }
+ }
+}
+
+static void window_motion(int x, int y)
+{
+ const int but = V.mouseBut0 ? 0 : 2;
+ const int distX = x - V.mouseX;
+ const int distY = y - V.mouseY;
+
+ if (V.motion)
+ V.motion(distX, distY, but);
+
+ V.mouseX = x;
+ V.mouseY = y;
+}
+
+bool window_opengl_context_enable()
+{
+ V.gl_context_mutex.lock();
+ SDL_GL_MakeCurrent(V.window, V.gl_context);
+ return true;
+}
+
+void window_opengl_context_disable()
+{
+ SDL_GL_MakeCurrent(V.window, nullptr);
+ V.gl_context_mutex.unlock();
+}
+
+void window_main_loop(const char *title,
+ int width,
+ int height,
+ WindowInitFunc initf,
+ WindowExitFunc exitf,
+ WindowResizeFunc resize,
+ WindowDisplayFunc display,
+ WindowKeyboardFunc keyboard,
+ WindowMotionFunc motion)
+{
+ V.width = width;
+ V.height = height;
+ V.first_display = true;
+ V.redraw = false;
+ V.initf = initf;
+ V.exitf = exitf;
+ V.resize = resize;
+ V.display = display;
+ V.keyboard = keyboard;
+ V.motion = motion;
+
+ SDL_Init(SDL_INIT_VIDEO);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+ V.window = SDL_CreateWindow(title,
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ width,
+ height,
+ SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
+ if (V.window == nullptr) {
+ fprintf(stderr, "Failed to create window: %s\n", SDL_GetError());
+ return;
+ }
+
+ SDL_RaiseWindow(V.window);
+
+ V.gl_context = SDL_GL_CreateContext(V.window);
+ glewInit();
+ SDL_GL_MakeCurrent(V.window, nullptr);
+
+ window_reshape(width, height);
+ window_display();
+
+ while (true) {
+ bool quit = false;
+ SDL_Event event;
+ while (!quit && SDL_PollEvent(&event)) {
+ if (event.type == SDL_TEXTINPUT) {
+ quit = window_keyboard(event.text.text[0]);
+ }
+ else if (event.type == SDL_MOUSEMOTION) {
+ window_motion(event.motion.x, event.motion.y);
+ }
+ else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
+ window_mouse(event.button.button, event.button.state, event.button.x, event.button.y);
+ }
+ else if (event.type == SDL_WINDOWEVENT) {
+ if (event.window.event == SDL_WINDOWEVENT_RESIZED ||
+ event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
+ window_reshape(event.window.data1, event.window.data2);
+ }
+ }
+ else if (event.type == SDL_QUIT) {
+ if (V.exitf) {
+ V.exitf();
+ }
+ quit = true;
+ }
+ }
+
+ if (quit) {
+ break;
+ }
+
+ if (V.redraw) {
+ V.redraw = false;
+ window_display();
+ }
+
+ SDL_WaitEventTimeout(NULL, 100);
+ }
+
+ SDL_GL_DeleteContext(V.gl_context);
+ SDL_DestroyWindow(V.window);
+ SDL_Quit();
+}
+
+void window_redraw()
+{
+ V.redraw = true;
+}
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/app/opengl/window.h b/intern/cycles/app/opengl/window.h
new file mode 100644
index 00000000000..531b5cab3fc
--- /dev/null
+++ b/intern/cycles/app/opengl/window.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#pragma once
+
+/* Functions to display a simple OpenGL window using SDL, simplified to the
+ * bare minimum we need to reduce boilerplate code in tests apps. */
+
+CCL_NAMESPACE_BEGIN
+
+typedef void (*WindowInitFunc)();
+typedef void (*WindowExitFunc)();
+typedef void (*WindowResizeFunc)(int width, int height);
+typedef void (*WindowDisplayFunc)();
+typedef void (*WindowKeyboardFunc)(unsigned char key);
+typedef void (*WindowMotionFunc)(int x, int y, int button);
+
+void window_main_loop(const char *title,
+ int width,
+ int height,
+ WindowInitFunc initf,
+ WindowExitFunc exitf,
+ WindowResizeFunc resize,
+ WindowDisplayFunc display,
+ WindowKeyboardFunc keyboard,
+ WindowMotionFunc motion);
+
+void window_display_info(const char *info);
+void window_display_help();
+void window_redraw();
+
+bool window_opengl_context_enable();
+void window_opengl_context_disable();
+
+CCL_NAMESPACE_END