Cycles: use GPU module for viewport display

To make GPU backends other than OpenGL work. Adds required pixel buffer and
fence objects to GPU module.

Authored by Apple: Michael Parkin-White

Ref T96261
Ref T92212

Reviewed By: fclem, brecht

Differential Revision: https://developer.blender.org/D16042
This commit is contained in:
Jason Fielder
2022-12-01 15:33:54 +01:00
committed by Brecht Van Lommel
parent b5ebc9bb24
commit b132e3b3ce
26 changed files with 947 additions and 389 deletions

View File

@@ -5,9 +5,14 @@
#include "device/device.h"
#include "util/log.h"
#include "util/math.h"
#include "util/opengl.h"
#include "GPU_platform.h"
#include "GPU_context.h"
#include "GPU_immediate.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "RE_engine.h"
@@ -30,8 +35,9 @@ unique_ptr<BlenderDisplayShader> BlenderDisplayShader::create(BL::RenderEngine &
int BlenderDisplayShader::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);
GPUShader *shader_program = get_shader_program();
position_attribute_location_ = GPU_shader_get_attribute(shader_program,
position_attribute_name);
}
return position_attribute_location_;
}
@@ -39,8 +45,9 @@ int BlenderDisplayShader::get_position_attrib_location()
int BlenderDisplayShader::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);
GPUShader *shader_program = get_shader_program();
tex_coord_attribute_location_ = GPU_shader_get_attribute(shader_program,
tex_coord_attribute_name);
}
return tex_coord_attribute_location_;
}
@@ -79,100 +86,42 @@ static const char *FALLBACK_FRAGMENT_SHADER =
" fragColor = texture(image_texture, texCoord_interp);\n"
"}\n\0";
static void shader_print_errors(const char *task, const char *log, const char *code)
static GPUShader *compile_fallback_shader(void)
{
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;
/* NOTE: Compilation errors are logged to console. */
GPUShader *shader = GPU_shader_create(FALLBACK_VERTEX_SHADER,
FALLBACK_FRAGMENT_SHADER,
nullptr,
nullptr,
nullptr,
"FallbackCyclesBlitShader");
return shader;
}
static int compile_fallback_shader(void)
{
const struct Shader {
const char *source;
const GLenum type;
} shaders[2] = {{FALLBACK_VERTEX_SHADER, GL_VERTEX_SHADER},
{FALLBACK_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);
/* TODO(sergey): Find a way to nicely de-duplicate the error checking. */
GLint link_status;
glGetProgramiv(program, GL_LINK_STATUS, &link_status);
if (!link_status) {
GLchar log[5000];
GLsizei length = 0;
/* TODO(sergey): Is it really program passed to glGetShaderInfoLog? */
glGetShaderInfoLog(program, sizeof(log), &length, log);
shader_print_errors("linking", log, FALLBACK_VERTEX_SHADER);
shader_print_errors("linking", log, FALLBACK_FRAGMENT_SHADER);
return 0;
}
return program;
}
void BlenderFallbackDisplayShader::bind(int width, int height)
GPUShader *BlenderFallbackDisplayShader::bind(int width, int height)
{
create_shader_if_needed();
if (!shader_program_) {
return;
return nullptr;
}
glUseProgram(shader_program_);
glUniform1i(image_texture_location_, 0);
glUniform2f(fullscreen_location_, width, height);
/* Bind shader now to enable uniform assignment. */
GPU_shader_bind(shader_program_);
GPU_shader_uniform_int(shader_program_, image_texture_location_, 0);
float size[2];
size[0] = width;
size[1] = height;
GPU_shader_uniform_vector(shader_program_, fullscreen_location_, 2, 1, size);
return shader_program_;
}
void BlenderFallbackDisplayShader::unbind()
{
GPU_shader_unbind();
}
uint BlenderFallbackDisplayShader::get_shader_program()
GPUShader *BlenderFallbackDisplayShader::get_shader_program()
{
return shader_program_;
}
@@ -187,19 +136,18 @@ void BlenderFallbackDisplayShader::create_shader_if_needed()
shader_program_ = compile_fallback_shader();
if (!shader_program_) {
LOG(ERROR) << "Failed to compile fallback shader";
return;
}
glUseProgram(shader_program_);
image_texture_location_ = glGetUniformLocation(shader_program_, "image_texture");
image_texture_location_ = GPU_shader_get_uniform(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");
fullscreen_location_ = GPU_shader_get_uniform(shader_program_, "fullscreen");
if (fullscreen_location_ < 0) {
LOG(ERROR) << "Shader doesn't contain the 'fullscreen' uniform.";
destroy_shader();
@@ -209,8 +157,10 @@ void BlenderFallbackDisplayShader::create_shader_if_needed()
void BlenderFallbackDisplayShader::destroy_shader()
{
glDeleteProgram(shader_program_);
shader_program_ = 0;
if (shader_program_) {
GPU_shader_free(shader_program_);
shader_program_ = nullptr;
}
}
/* --------------------------------------------------------------------
@@ -224,9 +174,10 @@ BlenderDisplaySpaceShader::BlenderDisplaySpaceShader(BL::RenderEngine &b_engine,
DCHECK(b_engine_.support_display_space_shader(b_scene_));
}
void BlenderDisplaySpaceShader::bind(int /*width*/, int /*height*/)
GPUShader *BlenderDisplaySpaceShader::bind(int /*width*/, int /*height*/)
{
b_engine_.bind_display_space_shader(b_scene_);
return GPU_shader_get_bound();
}
void BlenderDisplaySpaceShader::unbind()
@@ -234,12 +185,11 @@ void BlenderDisplaySpaceShader::unbind()
b_engine_.unbind_display_space_shader();
}
uint BlenderDisplaySpaceShader::get_shader_program()
GPUShader *BlenderDisplaySpaceShader::get_shader_program()
{
if (!shader_program_) {
glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<int *>(&shader_program_));
shader_program_ = GPU_shader_get_bound();
}
if (!shader_program_) {
LOG(ERROR) << "Error retrieving shader program for display space shader.";
}
@@ -252,34 +202,34 @@ uint BlenderDisplaySpaceShader::get_shader_program()
*/
/* Higher level representation of a texture from the graphics library. */
class GLTexture {
class DisplayGPUTexture {
public:
/* Global counter for all allocated OpenGL textures used by instances of this class. */
/* Global counter for all allocated GPUTextures used by instances of this class. */
static inline std::atomic<int> num_used = 0;
GLTexture() = default;
DisplayGPUTexture() = default;
~GLTexture()
~DisplayGPUTexture()
{
assert(gl_id == 0);
assert(gpu_texture == nullptr);
}
GLTexture(const GLTexture &other) = delete;
GLTexture &operator=(GLTexture &other) = delete;
DisplayGPUTexture(const DisplayGPUTexture &other) = delete;
DisplayGPUTexture &operator=(DisplayGPUTexture &other) = delete;
GLTexture(GLTexture &&other) noexcept
: gl_id(other.gl_id), width(other.width), height(other.height)
DisplayGPUTexture(DisplayGPUTexture &&other) noexcept
: gpu_texture(other.gpu_texture), width(other.width), height(other.height)
{
other.reset();
}
GLTexture &operator=(GLTexture &&other)
DisplayGPUTexture &operator=(DisplayGPUTexture &&other)
{
if (this == &other) {
return *this;
}
gl_id = other.gl_id;
gpu_texture = other.gpu_texture;
width = other.width;
height = other.height;
@@ -288,55 +238,56 @@ class GLTexture {
return *this;
}
bool gl_resources_ensure()
bool gpu_resources_ensure()
{
if (gl_id) {
if (gpu_texture) {
return true;
}
/* Create texture. */
glGenTextures(1, &gl_id);
if (!gl_id) {
/* Texture must have a minimum size of 1x1. */
gpu_texture = GPU_texture_create_2d(
"CyclesBlitTexture", max(width, 1), max(height, 1), 1, GPU_RGBA16F, nullptr);
if (!gpu_texture) {
LOG(ERROR) << "Error creating texture.";
return false;
}
/* Configure the texture. */
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
/* Clamp to edge so that precision issues when zoomed out (which forces linear interpolation)
* does not cause unwanted repetition. */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
GPU_texture_filter_mode(gpu_texture, false);
GPU_texture_wrap_mode(gpu_texture, false, true);
++num_used;
return true;
}
void gl_resources_destroy()
void gpu_resources_destroy()
{
if (!gl_id) {
if (gpu_texture == nullptr) {
return;
}
glDeleteTextures(1, &gl_id);
GPU_TEXTURE_FREE_SAFE(gpu_texture);
reset();
--num_used;
}
/* OpenGL resource IDs of the texture.
void ensure_size(uint texture_width, uint texture_height)
{
if (width != texture_width || height != texture_height) {
gpu_resources_destroy();
width = texture_width;
height = texture_height;
gpu_resources_ensure();
}
}
/* Texture resource allocated by the GPU module.
*
* NOTE: Allocated on the render engine's context. */
uint gl_id = 0;
GPUTexture *gpu_texture = nullptr;
/* Dimensions of the texture in pixels. */
int width = 0;
@@ -345,41 +296,41 @@ class GLTexture {
protected:
void reset()
{
gl_id = 0;
gpu_texture = nullptr;
width = 0;
height = 0;
}
};
/* Higher level representation of a Pixel Buffer Object (PBO) from the graphics library. */
class GLPixelBufferObject {
class DisplayGPUPixelBuffer {
public:
/* Global counter for all allocated OpenGL PBOs used by instances of this class. */
/* Global counter for all allocated GPU module PBOs used by instances of this class. */
static inline std::atomic<int> num_used = 0;
GLPixelBufferObject() = default;
DisplayGPUPixelBuffer() = default;
~GLPixelBufferObject()
~DisplayGPUPixelBuffer()
{
assert(gl_id == 0);
assert(gpu_pixel_buffer == nullptr);
}
GLPixelBufferObject(const GLPixelBufferObject &other) = delete;
GLPixelBufferObject &operator=(GLPixelBufferObject &other) = delete;
DisplayGPUPixelBuffer(const DisplayGPUPixelBuffer &other) = delete;
DisplayGPUPixelBuffer &operator=(DisplayGPUPixelBuffer &other) = delete;
GLPixelBufferObject(GLPixelBufferObject &&other) noexcept
: gl_id(other.gl_id), width(other.width), height(other.height)
DisplayGPUPixelBuffer(DisplayGPUPixelBuffer &&other) noexcept
: gpu_pixel_buffer(other.gpu_pixel_buffer), width(other.width), height(other.height)
{
other.reset();
}
GLPixelBufferObject &operator=(GLPixelBufferObject &&other)
DisplayGPUPixelBuffer &operator=(DisplayGPUPixelBuffer &&other)
{
if (this == &other) {
return *this;
}
gl_id = other.gl_id;
gpu_pixel_buffer = other.gpu_pixel_buffer;
width = other.width;
height = other.height;
@@ -388,14 +339,34 @@ class GLPixelBufferObject {
return *this;
}
bool gl_resources_ensure()
void ensure_size(uint new_width, uint new_height)
{
if (gl_id) {
return true;
size_t required_size = sizeof(half4) * new_width * new_height * 4;
if (gpu_pixel_buffer) {
if (new_width != width || new_height != height ||
GPU_pixel_buffer_size(gpu_pixel_buffer) < required_size) {
GPU_pixel_buffer_free(gpu_pixel_buffer);
gpu_pixel_buffer = nullptr;
}
}
glGenBuffers(1, &gl_id);
if (!gl_id) {
/* Update size. */
width = new_width;
height = new_height;
/* Create pixel buffer if not already created. */
if (!gpu_pixel_buffer) {
gpu_pixel_buffer = GPU_pixel_buffer_create(required_size);
}
}
bool gpu_resources_ensure()
{
/* Create pixel buffer using current size. */
ensure_size(width, height);
if (gpu_pixel_buffer == nullptr) {
LOG(ERROR) << "Error creating texture pixel buffer object.";
return false;
}
@@ -405,23 +376,24 @@ class GLPixelBufferObject {
return true;
}
void gl_resources_destroy()
void gpu_resources_destroy()
{
if (!gl_id) {
if (!gpu_pixel_buffer) {
return;
}
glDeleteBuffers(1, &gl_id);
GPU_pixel_buffer_free(gpu_pixel_buffer);
gpu_pixel_buffer = nullptr;
reset();
--num_used;
}
/* OpenGL resource IDs of the PBO.
/* Pixel Buffer Object allocated by the GPU module.
*
* NOTE: Allocated on the render engine's context. */
uint gl_id = 0;
GPUPixelBuffer *gpu_pixel_buffer = nullptr;
/* Dimensions of the PBO. */
int width = 0;
@@ -430,7 +402,7 @@ class GLPixelBufferObject {
protected:
void reset()
{
gl_id = 0;
gpu_pixel_buffer = 0;
width = 0;
height = 0;
}
@@ -448,28 +420,28 @@ class DrawTile {
DrawTile &operator=(DrawTile &&other) = default;
bool gl_resources_ensure()
bool gpu_resources_ensure()
{
if (!texture.gl_resources_ensure()) {
gl_resources_destroy();
if (!texture.gpu_resources_ensure()) {
gpu_resources_destroy();
return false;
}
return true;
}
void gl_resources_destroy()
void gpu_resources_destroy()
{
texture.gl_resources_destroy();
texture.gpu_resources_destroy();
}
inline bool ready_to_draw() const
{
return texture.gl_id != 0;
return texture.gpu_texture != 0;
}
/* Texture which contains pixels of the tile. */
GLTexture texture;
DisplayGPUTexture texture;
/* Display parameters the texture of this tile has been updated for. */
BlenderDisplayDriver::Params params;
@@ -477,24 +449,24 @@ class DrawTile {
class DrawTileAndPBO {
public:
bool gl_resources_ensure()
bool gpu_resources_ensure()
{
if (!tile.gl_resources_ensure() || !buffer_object.gl_resources_ensure()) {
gl_resources_destroy();
if (!tile.gpu_resources_ensure() || !buffer_object.gpu_resources_ensure()) {
gpu_resources_destroy();
return false;
}
return true;
}
void gl_resources_destroy()
void gpu_resources_destroy()
{
tile.gl_resources_destroy();
buffer_object.gl_resources_destroy();
tile.gpu_resources_destroy();
buffer_object.gpu_resources_destroy();
}
DrawTile tile;
GLPixelBufferObject buffer_object;
DisplayGPUPixelBuffer buffer_object;
bool need_update_texture_pixels = false;
};
@@ -513,36 +485,12 @@ struct BlenderDisplayDriver::Tiles {
void gl_resources_destroy_and_clear()
{
for (DrawTile &tile : tiles) {
tile.gl_resources_destroy();
tile.gpu_resources_destroy();
}
tiles.clear();
}
} finished_tiles;
/* OpenGL vertex buffer needed for drawing. */
uint gl_vertex_buffer = 0;
bool gl_resources_ensure()
{
if (!gl_vertex_buffer) {
glGenBuffers(1, &gl_vertex_buffer);
if (!gl_vertex_buffer) {
LOG(ERROR) << "Error allocating tile VBO.";
return false;
}
}
return true;
}
void gl_resources_destroy()
{
if (gl_vertex_buffer) {
glDeleteBuffers(1, &gl_vertex_buffer);
gl_vertex_buffer = 0;
}
}
};
BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine,
@@ -590,7 +538,7 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
/* Note that it's the responsibility of BlenderDisplayDriver 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.gpu_context_lock.
* When enabling the OpenGL/GPU context, it uses an internal mutex lock DST.gpu_context_lock.
* This same lock is also held when do_draw() is called, which together ensure mutual
* exclusion.
*
@@ -599,12 +547,10 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
return false;
}
if (gl_render_sync_) {
glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
}
GPU_fence_wait(gpu_render_sync_);
DrawTile &current_tile = tiles_->current_tile.tile;
GLPixelBufferObject &current_tile_buffer_object = tiles_->current_tile.buffer_object;
DisplayGPUPixelBuffer &current_tile_buffer_object = tiles_->current_tile.buffer_object;
/* Clear storage of all finished tiles when display clear is requested.
* Do it when new tile data is provided to handle the display clear flag in a single place.
@@ -614,29 +560,14 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
need_clear_ = false;
}
if (!tiles_->gl_resources_ensure()) {
tiles_->gl_resources_destroy();
gpu_context_disable();
return false;
}
if (!tiles_->current_tile.gl_resources_ensure()) {
tiles_->current_tile.gl_resources_destroy();
if (!tiles_->current_tile.gpu_resources_ensure()) {
tiles_->current_tile.gpu_resources_destroy();
gpu_context_disable();
return false;
}
/* Update texture dimensions if needed. */
if (current_tile.texture.width != texture_width ||
current_tile.texture.height != texture_height) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, current_tile.texture.gl_id);
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA16F, texture_width, texture_height, 0, GL_RGBA, GL_HALF_FLOAT, 0);
current_tile.texture.width = texture_width;
current_tile.texture.height = texture_height;
glBindTexture(GL_TEXTURE_2D, 0);
}
current_tile.texture.ensure_size(texture_width, texture_height);
/* Update PBO dimensions if needed.
*
@@ -649,16 +580,7 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
* mode faster. */
const int buffer_width = params.size.x;
const int buffer_height = params.size.y;
if (current_tile_buffer_object.width != buffer_width ||
current_tile_buffer_object.height != buffer_height) {
const size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, current_tile_buffer_object.gl_id);
glBufferData(GL_PIXEL_UNPACK_BUFFER, size_in_bytes, 0, GL_DYNAMIC_DRAW);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
current_tile_buffer_object.width = buffer_width;
current_tile_buffer_object.height = buffer_height;
}
current_tile_buffer_object.ensure_size(buffer_width, buffer_height);
/* Store an updated parameters of the current tile.
* In theory it is only needed once per update of the tile, but doing it on every update is
@@ -670,19 +592,21 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
static void update_tile_texture_pixels(const DrawTileAndPBO &tile)
{
const GLTexture &texture = tile.tile.texture;
const DisplayGPUTexture &texture = tile.tile.texture;
DCHECK_NE(tile.buffer_object.gl_id, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture.gl_id);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, tile.buffer_object.gl_id);
glTexSubImage2D(
GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, GL_RGBA, GL_HALF_FLOAT, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
if (!DCHECK_NOTNULL(tile.buffer_object.gpu_pixel_buffer)) {
LOG(ERROR) << "Display driver tile pixel buffer unavailable.";
return;
}
GPU_texture_update_sub_from_pixel_buffer(texture.gpu_texture,
GPU_DATA_HALF_FLOAT,
tile.buffer_object.gpu_pixel_buffer,
0,
0,
0,
texture.width,
texture.height,
0);
}
void BlenderDisplayDriver::update_end()
@@ -709,8 +633,10 @@ void BlenderDisplayDriver::update_end()
update_tile_texture_pixels(tiles_->current_tile);
}
gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
/* Ensure GPU fence exists to synchronize upload. */
GPU_fence_signal(gpu_upload_sync_);
GPU_flush();
gpu_context_disable();
}
@@ -721,26 +647,26 @@ void BlenderDisplayDriver::update_end()
half4 *BlenderDisplayDriver::map_texture_buffer()
{
const uint pbo_gl_id = tiles_->current_tile.buffer_object.gl_id;
DCHECK_NE(pbo_gl_id, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_gl_id);
half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(
glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
GPUPixelBuffer *pix_buf = tiles_->current_tile.buffer_object.gpu_pixel_buffer;
if (!DCHECK_NOTNULL(pix_buf)) {
LOG(ERROR) << "Display driver tile pixel buffer unavailable.";
return nullptr;
}
half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(GPU_pixel_buffer_map(pix_buf));
if (!mapped_rgba_pixels) {
LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object.";
}
return mapped_rgba_pixels;
}
void BlenderDisplayDriver::unmap_texture_buffer()
{
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
GPUPixelBuffer *pix_buf = tiles_->current_tile.buffer_object.gpu_pixel_buffer;
if (!DCHECK_NOTNULL(pix_buf)) {
LOG(ERROR) << "Display driver tile pixel buffer unavailable.";
return;
}
GPU_pixel_buffer_unmap(pix_buf);
}
/* --------------------------------------------------------------------
@@ -753,7 +679,8 @@ BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get
interop_dst.buffer_width = tiles_->current_tile.buffer_object.width;
interop_dst.buffer_height = tiles_->current_tile.buffer_object.height;
interop_dst.opengl_pbo_id = tiles_->current_tile.buffer_object.gl_id;
interop_dst.opengl_pbo_id = (int)GPU_pixel_buffer_get_native_handle(
tiles_->current_tile.buffer_object.gpu_pixel_buffer);
return interop_dst;
}
@@ -786,7 +713,9 @@ void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y)
* This buffer is used to render texture in the viewport.
*
* NOTE: The buffer needs to be bound. */
static void vertex_buffer_update(const DisplayDriver::Params &params)
static void vertex_draw(const DisplayDriver::Params &params,
int texcoord_attribute,
int position_attribute)
{
const int x = params.full_offset.x;
const int y = params.full_offset.y;
@@ -794,67 +723,40 @@ static void vertex_buffer_update(const DisplayDriver::Params &params)
const int width = params.size.x;
const int height = params.size.y;
/* 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);
immBegin(GPU_PRIM_TRI_STRIP, 4);
float *vpointer = reinterpret_cast<float *>(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY));
if (!vpointer) {
return;
}
immAttr2f(texcoord_attribute, 1.0f, 0.0f);
immVertex2f(position_attribute, x + width, y);
vpointer[0] = 0.0f;
vpointer[1] = 0.0f;
vpointer[2] = x;
vpointer[3] = y;
immAttr2f(texcoord_attribute, 1.0f, 1.0f);
immVertex2f(position_attribute, x + width, y + height);
vpointer[4] = 1.0f;
vpointer[5] = 0.0f;
vpointer[6] = x + width;
vpointer[7] = y;
immAttr2f(texcoord_attribute, 0.0f, 0.0f);
immVertex2f(position_attribute, x, y);
vpointer[8] = 1.0f;
vpointer[9] = 1.0f;
vpointer[10] = x + width;
vpointer[11] = y + height;
immAttr2f(texcoord_attribute, 0.0f, 1.0f);
immVertex2f(position_attribute, x, y + height);
vpointer[12] = 0.0f;
vpointer[13] = 1.0f;
vpointer[14] = x;
vpointer[15] = y + height;
glUnmapBuffer(GL_ARRAY_BUFFER);
immEnd();
}
static void draw_tile(const float2 &zoom,
const int texcoord_attribute,
const int position_attribute,
const DrawTile &draw_tile,
const uint gl_vertex_buffer)
const DrawTile &draw_tile)
{
if (!draw_tile.ready_to_draw()) {
return;
}
const GLTexture &texture = draw_tile.texture;
const DisplayGPUTexture &texture = draw_tile.texture;
DCHECK_NE(texture.gl_id, 0);
DCHECK_NE(gl_vertex_buffer, 0);
if (!DCHECK_NOTNULL(texture.gpu_texture)) {
LOG(ERROR) << "Display driver tile GPU texture resource unavailable.";
return;
}
glBindBuffer(GL_ARRAY_BUFFER, gl_vertex_buffer);
/* Draw at the parameters for which the texture has been updated for. This allows to always draw
* texture during bordered-rendered camera view without flickering. The validness of the display
* parameters for a texture is guaranteed by the initial "clear" state which makes drawing to
* have an early output.
*
* Such approach can cause some extra "jelly" effect during panning, but it is not more jelly
* than overlay of selected objects. Also, it's possible to redraw texture at an intersection of
* the texture draw parameters and the latest updated draw parameters (although, complexity of
* doing it might not worth it. */
vertex_buffer_update(draw_tile.params);
glBindTexture(GL_TEXTURE_2D, texture.gl_id);
GPU_texture_bind(texture.gpu_texture, 0);
/* Trick to keep sharp rendering without jagged edges on all GPUs.
*
@@ -868,26 +770,26 @@ static void draw_tile(const float2 &zoom,
const float zoomed_height = draw_tile.params.size.y * zoom.y;
if (texture.width != draw_tile.params.size.x || texture.height != draw_tile.params.size.y) {
/* Resolution divider is different from 1, force nearest interpolation. */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
GPU_texture_filter_mode(texture.gpu_texture, false);
}
else if (zoomed_width - draw_tile.params.size.x > 0.5f ||
zoomed_height - draw_tile.params.size.y > 0.5f) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
GPU_texture_filter_mode(texture.gpu_texture, false);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GPU_texture_filter_mode(texture.gpu_texture, true);
}
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);
/* Draw at the parameters for which the texture has been updated for. This allows to always draw
* texture during bordered-rendered camera view without flickering. The validness of the display
* parameters for a texture is guaranteed by the initial "clear" state which makes drawing to
* have an early output.
*
* Such approach can cause some extra "jelly" effect during panning, but it is not more jelly
* than overlay of selected objects. Also, it's possible to redraw texture at an intersection of
* the texture draw parameters and the latest updated draw parameters (although, complexity of
* doing it might not worth it. */
vertex_draw(draw_tile.params, texcoord_attribute, position_attribute);
}
void BlenderDisplayDriver::flush()
@@ -903,13 +805,8 @@ void BlenderDisplayDriver::flush()
return;
}
if (gl_upload_sync_) {
glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
}
if (gl_render_sync_) {
glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
}
GPU_fence_wait(gpu_upload_sync_);
GPU_fence_wait(gpu_render_sync_);
gpu_context_disable();
}
@@ -928,68 +825,56 @@ void BlenderDisplayDriver::draw(const Params &params)
return;
}
if (gl_upload_sync_) {
glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
}
GPU_fence_wait(gpu_upload_sync_);
GPU_blend(GPU_BLEND_ALPHA_PREMULT);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
GPUShader *active_shader = display_shader_->bind(params.full_size.x, params.full_size.y);
glActiveTexture(GL_TEXTURE0);
GPUVertFormat *format = immVertexFormat();
const int texcoord_attribute = GPU_vertformat_attr_add(
format, display_shader_->tex_coord_attribute_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
const int position_attribute = GPU_vertformat_attr_add(
format, display_shader_->position_attribute_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
/* NOTE: The VAO is to be allocated on the drawing context as it is not shared across contexts.
* Simplest is to allocate it on every redraw so that it is possible to destroy it from a
* correct context. */
GLuint vertex_array_object;
glGenVertexArrays(1, &vertex_array_object);
glBindVertexArray(vertex_array_object);
/* Note: Shader is bound again through IMM to register this shader with the imm module
* and perform required setup for IMM rendering. This is required as the IMM module
* needs to be aware of which shader is bound, and the main display shader
* is bound externally. */
immBindShader(active_shader);
display_shader_->bind(params.full_size.x, params.full_size.y);
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);
if (tiles_->current_tile.need_update_texture_pixels) {
update_tile_texture_pixels(tiles_->current_tile);
tiles_->current_tile.need_update_texture_pixels = false;
}
draw_tile(zoom_,
texcoord_attribute,
position_attribute,
tiles_->current_tile.tile,
tiles_->gl_vertex_buffer);
draw_tile(zoom_, texcoord_attribute, position_attribute, tiles_->current_tile.tile);
for (const DrawTile &tile : tiles_->finished_tiles.tiles) {
draw_tile(zoom_, texcoord_attribute, position_attribute, tile, tiles_->gl_vertex_buffer);
draw_tile(zoom_, texcoord_attribute, position_attribute, tile);
}
/* Reset IMM shader bind state. */
immUnbindProgram();
display_shader_->unbind();
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GPU_blend(GPU_BLEND_NONE);
glDeleteVertexArrays(1, &vertex_array_object);
glDisable(GL_BLEND);
gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
GPU_fence_signal(gpu_render_sync_);
GPU_flush();
gpu_context_unlock();
VLOG_DEVICE_STATS << "Display driver number of textures: " << GLTexture::num_used;
VLOG_DEVICE_STATS << "Display driver number of PBOs: " << GLPixelBufferObject::num_used;
VLOG_DEVICE_STATS << "Display driver number of textures: " << DisplayGPUTexture::num_used;
VLOG_DEVICE_STATS << "Display driver number of PBOs: " << DisplayGPUPixelBuffer::num_used;
}
void BlenderDisplayDriver::gpu_context_create()
{
if (!RE_engine_gpu_context_create(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data))) {
LOG(ERROR) << "Error creating OpenGL context.";
LOG(ERROR) << "Error creating GPU context.";
return;
}
/* Create global GPU resources for display driver. */
if (!gpu_resources_create()) {
LOG(ERROR) << "Error creating GPU resources for Cycles Display Driver.";
return;
}
}
@@ -1018,13 +903,43 @@ void BlenderDisplayDriver::gpu_context_unlock()
RE_engine_gpu_context_unlock(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data));
}
bool BlenderDisplayDriver::gpu_resources_create()
{
/* Ensure context is active for resource creation. */
if (!gpu_context_enable()) {
LOG(ERROR) << "Error enabling GPU context.";
return false;
}
gpu_upload_sync_ = GPU_fence_create();
gpu_render_sync_ = GPU_fence_create();
if (!DCHECK_NOTNULL(gpu_upload_sync_) || !DCHECK_NOTNULL(gpu_render_sync_)) {
LOG(ERROR) << "Error creating GPU synchronization primtiives.";
assert(0);
return false;
}
gpu_context_disable();
return true;
}
void BlenderDisplayDriver::gpu_resources_destroy()
{
gpu_context_enable();
tiles_->current_tile.gl_resources_destroy();
tiles_->current_tile.gpu_resources_destroy();
tiles_->finished_tiles.gl_resources_destroy_and_clear();
tiles_->gl_resources_destroy();
/* Fences. */
if (gpu_render_sync_) {
GPU_fence_free(gpu_render_sync_);
gpu_render_sync_ = nullptr;
}
if (gpu_upload_sync_) {
GPU_fence_free(gpu_upload_sync_);
gpu_upload_sync_ = nullptr;
}
gpu_context_disable();

View File

@@ -15,6 +15,10 @@
#include "util/unique_ptr.h"
#include "util/vector.h"
typedef struct GPUContext GPUContext;
typedef struct GPUFence GPUFence;
typedef struct GPUShader GPUShader;
CCL_NAMESPACE_BEGIN
/* Base class of shader used for display driver rendering. */
@@ -29,7 +33,7 @@ class BlenderDisplayShader {
BlenderDisplayShader() = default;
virtual ~BlenderDisplayShader() = default;
virtual void bind(int width, int height) = 0;
virtual GPUShader *bind(int width, int height) = 0;
virtual void unbind() = 0;
/* Get attribute location for position and texture coordinate respectively.
@@ -40,7 +44,7 @@ class BlenderDisplayShader {
protected:
/* Get program of this display shader.
* NOTE: The shader needs to be bound to have access to this. */
virtual uint get_shader_program() = 0;
virtual GPUShader *get_shader_program() = 0;
/* Cached values of various OpenGL resources. */
int position_attribute_location_ = -1;
@@ -51,16 +55,16 @@ class BlenderDisplayShader {
* display space shader. */
class BlenderFallbackDisplayShader : public BlenderDisplayShader {
public:
virtual void bind(int width, int height) override;
virtual GPUShader *bind(int width, int height) override;
virtual void unbind() override;
protected:
virtual uint get_shader_program() override;
virtual GPUShader *get_shader_program() override;
void create_shader_if_needed();
void destroy_shader();
uint shader_program_ = 0;
GPUShader *shader_program_ = 0;
int image_texture_location_ = -1;
int fullscreen_location_ = -1;
@@ -73,17 +77,17 @@ class BlenderDisplaySpaceShader : public BlenderDisplayShader {
public:
BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, BL::Scene &b_scene);
virtual void bind(int width, int height) override;
virtual GPUShader *bind(int width, int height) override;
virtual void unbind() override;
protected:
virtual uint get_shader_program() override;
virtual GPUShader *get_shader_program() override;
BL::RenderEngine b_engine_;
BL::Scene &b_scene_;
/* Cached values of various OpenGL resources. */
uint shader_program_ = 0;
GPUShader *shader_program_ = nullptr;
};
/* Display driver implementation which is specific for Blender viewport integration. */
@@ -122,6 +126,9 @@ class BlenderDisplayDriver : public DisplayDriver {
void gpu_context_lock();
void gpu_context_unlock();
/* Create GPU resources used by the dispaly driver. */
bool gpu_resources_create();
/* Destroy all GPU resources which are being used by this object. */
void gpu_resources_destroy();
@@ -137,8 +144,8 @@ class BlenderDisplayDriver : public DisplayDriver {
struct Tiles;
unique_ptr<Tiles> tiles_;
void *gl_render_sync_ = nullptr;
void *gl_upload_sync_ = nullptr;
GPUFence *gpu_render_sync_ = nullptr;
GPUFence *gpu_upload_sync_ = nullptr;
float2 zoom_ = make_float2(1.0f, 1.0f);
};

View File

@@ -97,6 +97,7 @@ void GPU_shader_free(GPUShader *shader);
void GPU_shader_bind(GPUShader *shader);
void GPU_shader_unbind(void);
GPUShader *GPU_shader_get_bound(void);
const char *GPU_shader_get_name(GPUShader *shader);

View File

@@ -8,6 +8,9 @@
#include "BLI_utildefines.h"
/** Opaque type hiding blender::gpu::Fence. */
typedef struct GPUFence GPUFence;
typedef enum eGPUWriteMask {
GPU_WRITE_NONE = 0,
GPU_WRITE_RED = (1 << 0),
@@ -196,6 +199,11 @@ bool GPU_bgl_get(void);
void GPU_memory_barrier(eGPUBarrier barrier);
GPUFence *GPU_fence_create(void);
void GPU_fence_free(GPUFence *fence);
void GPU_fence_signal(GPUFence *fence);
void GPU_fence_wait(GPUFence *fence);
#ifdef __cplusplus
}
#endif

View File

@@ -16,6 +16,9 @@ struct GPUVertBuf;
/** Opaque type hiding blender::gpu::Texture. */
typedef struct GPUTexture GPUTexture;
/** Opaque type hiding blender::gpu::PixelBuffer. */
typedef struct GPUPixelBuffer GPUPixelBuffer;
/**
* GPU Samplers state
* - Specify the sampler state to bind a texture with.
@@ -284,6 +287,17 @@ void GPU_texture_update_sub(GPUTexture *tex,
int width,
int height,
int depth);
/* Update from API Buffer. */
void GPU_texture_update_sub_from_pixel_buffer(GPUTexture *tex,
eGPUDataFormat data_format,
GPUPixelBuffer *pix_buf,
int offset_x,
int offset_y,
int offset_z,
int width,
int height,
int depth);
/**
* Makes data interpretation aware of the source layout.
* Skipping pixels correctly when changing rows when doing partial update.
@@ -366,6 +380,15 @@ void GPU_texture_get_mipmap_size(GPUTexture *tex, int lvl, int *size);
size_t GPU_texture_component_len(eGPUTextureFormat format);
size_t GPU_texture_dataformat_size(eGPUDataFormat data_format);
/* GPU Pixel Buffer. */
GPUPixelBuffer *GPU_pixel_buffer_create(uint size);
void GPU_pixel_buffer_free(GPUPixelBuffer *pix_buf);
void *GPU_pixel_buffer_map(GPUPixelBuffer *pix_buf);
void GPU_pixel_buffer_unmap(GPUPixelBuffer *pix_buf);
uint GPU_pixel_buffer_size(GPUPixelBuffer *pix_buf);
int64_t GPU_pixel_buffer_get_native_handle(GPUPixelBuffer *pix_buf);
#ifdef __cplusplus
}
#endif

View File

@@ -18,8 +18,10 @@ class Context;
class Batch;
class DrawList;
class Fence;
class FrameBuffer;
class IndexBuf;
class PixelBuffer;
class QueryPool;
class Shader;
class Texture;
@@ -42,8 +44,10 @@ class GPUBackend {
virtual Batch *batch_alloc() = 0;
virtual DrawList *drawlist_alloc(int list_length) = 0;
virtual Fence *fence_alloc() = 0;
virtual FrameBuffer *framebuffer_alloc(const char *name) = 0;
virtual IndexBuf *indexbuf_alloc() = 0;
virtual PixelBuffer *pixelbuf_alloc(uint size) = 0;
virtual QueryPool *querypool_alloc() = 0;
virtual Shader *shader_alloc(const char *name) = 0;
virtual Texture *texture_alloc(const char *name) = 0;

View File

@@ -527,6 +527,15 @@ void GPU_shader_unbind()
#endif
}
GPUShader *GPU_shader_get_bound()
{
Context *ctx = Context::get();
if (ctx) {
return wrap(ctx->shader);
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -16,6 +16,7 @@
#include "GPU_state.h"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_state_private.hh"
@@ -373,6 +374,27 @@ void GPU_memory_barrier(eGPUBarrier barrier)
Context::get()->state_manager->issue_barrier(barrier);
}
GPUFence *GPU_fence_create()
{
Fence *fence = GPUBackend::get()->fence_alloc();
return wrap(fence);
}
void GPU_fence_free(GPUFence *fence)
{
delete unwrap(fence);
}
void GPU_fence_signal(GPUFence *fence)
{
unwrap(fence)->signal();
}
void GPU_fence_wait(GPUFence *fence)
{
unwrap(fence)->wait();
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -160,5 +160,34 @@ class StateManager {
virtual void texture_unpack_row_length_set(uint len) = 0;
};
/**
* GPUFence.
*/
class Fence {
protected:
bool signalled_ = false;
public:
Fence(){};
virtual ~Fence(){};
virtual void signal() = 0;
virtual void wait() = 0;
};
/* Syntactic sugar. */
static inline GPUFence *wrap(Fence *pixbuf)
{
return reinterpret_cast<GPUFence *>(pixbuf);
}
static inline Fence *unwrap(GPUFence *pixbuf)
{
return reinterpret_cast<Fence *>(pixbuf);
}
static inline const Fence *unwrap(const GPUFence *pixbuf)
{
return reinterpret_cast<const Fence *>(pixbuf);
}
} // namespace gpu
} // namespace blender

View File

@@ -455,6 +455,21 @@ void GPU_texture_update_sub(GPUTexture *tex,
reinterpret_cast<Texture *>(tex)->update_sub(0, offset, extent, data_format, pixels);
}
void GPU_texture_update_sub_from_pixel_buffer(GPUTexture *tex,
eGPUDataFormat data_format,
GPUPixelBuffer *pix_buf,
int offset_x,
int offset_y,
int offset_z,
int width,
int height,
int depth)
{
int offset[3] = {offset_x, offset_y, offset_z};
int extent[3] = {width, height, depth};
reinterpret_cast<Texture *>(tex)->update_sub(offset, extent, data_format, pix_buf);
}
void *GPU_texture_read(GPUTexture *tex_, eGPUDataFormat data_format, int miplvl)
{
Texture *tex = reinterpret_cast<Texture *>(tex_);
@@ -824,6 +839,53 @@ void GPU_texture_get_mipmap_size(GPUTexture *tex, int lvl, int *r_size)
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPU Pixel Buffer
*
* Pixel buffer utility functions.
* \{ */
GPUPixelBuffer *GPU_pixel_buffer_create(uint size)
{
/* Ensure buffer satifies the alignment of 256 bytes for copying
* data between buffers and textures. As specified in:
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
*
* Ensuring minimal size across all platforms handles cases for small-sized
* textures and avoids issues with zero-sized buffers. */
size = ceil_to_multiple_ul(size, 256);
PixelBuffer *pixbuf = GPUBackend::get()->pixelbuf_alloc(size);
return wrap(pixbuf);
}
void GPU_pixel_buffer_free(GPUPixelBuffer *pix_buf)
{
PixelBuffer *handle = unwrap(pix_buf);
delete handle;
}
void *GPU_pixel_buffer_map(GPUPixelBuffer *pix_buf)
{
return reinterpret_cast<PixelBuffer *>(pix_buf)->map();
}
void GPU_pixel_buffer_unmap(GPUPixelBuffer *pix_buf)
{
reinterpret_cast<PixelBuffer *>(pix_buf)->unmap();
}
uint GPU_pixel_buffer_size(GPUPixelBuffer *pix_buf)
{
return reinterpret_cast<PixelBuffer *>(pix_buf)->get_size();
}
int64_t GPU_pixel_buffer_get_native_handle(GPUPixelBuffer *pix_buf)
{
return reinterpret_cast<PixelBuffer *>(pix_buf)->get_native_handle();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPU Sampler Objects
*

View File

@@ -129,6 +129,10 @@ class Texture {
virtual void update_sub(
int mip, int offset[3], int extent[3], eGPUDataFormat format, const void *data) = 0;
virtual void update_sub(int offset[3],
int extent[3],
eGPUDataFormat format,
GPUPixelBuffer *pixbuf) = 0;
/* TODO(fclem): Legacy. Should be removed at some point. */
virtual uint gl_bindcode_get() const = 0;
@@ -264,6 +268,35 @@ static inline const Texture *unwrap(const GPUTexture *vert)
return reinterpret_cast<const Texture *>(vert);
}
/* GPU pixel Buffer. */
class PixelBuffer {
protected:
uint size_ = 0;
public:
PixelBuffer(uint size) : size_(size){};
virtual ~PixelBuffer(){};
virtual void *map() = 0;
virtual void unmap() = 0;
virtual int64_t get_native_handle() = 0;
virtual uint get_size() = 0;
};
/* Syntactic sugar. */
static inline GPUPixelBuffer *wrap(PixelBuffer *pixbuf)
{
return reinterpret_cast<GPUPixelBuffer *>(pixbuf);
}
static inline PixelBuffer *unwrap(GPUPixelBuffer *pixbuf)
{
return reinterpret_cast<PixelBuffer *>(pixbuf);
}
static inline const PixelBuffer *unwrap(const GPUPixelBuffer *pixbuf)
{
return reinterpret_cast<const PixelBuffer *>(pixbuf);
}
#undef DEBUG_NAME_LEN
inline size_t to_bytesize(eGPUTextureFormat format)
@@ -405,6 +438,8 @@ inline size_t to_bytesize(eGPUDataFormat data_format)
switch (data_format) {
case GPU_DATA_UBYTE:
return 1;
case GPU_DATA_HALF_FLOAT:
return 2;
case GPU_DATA_FLOAT:
case GPU_DATA_INT:
case GPU_DATA_UINT:

View File

@@ -66,8 +66,10 @@ class MTLBackend : public GPUBackend {
Context *context_alloc(void *ghost_window, void *ghost_context) override;
Batch *batch_alloc() override;
DrawList *drawlist_alloc(int list_length) override;
Fence *fence_alloc() override;
FrameBuffer *framebuffer_alloc(const char *name) override;
IndexBuf *indexbuf_alloc() override;
PixelBuffer *pixelbuf_alloc(uint size) override;
QueryPool *querypool_alloc() override;
Shader *shader_alloc(const char *name) override;
Texture *texture_alloc(const char *name) override;

View File

@@ -55,6 +55,11 @@ DrawList *MTLBackend::drawlist_alloc(int list_length)
return new MTLDrawList(list_length);
};
Fence *MTLBackend::fence_alloc()
{
return new MTLFence();
};
FrameBuffer *MTLBackend::framebuffer_alloc(const char *name)
{
MTLContext *mtl_context = static_cast<MTLContext *>(
@@ -67,6 +72,11 @@ IndexBuf *MTLBackend::indexbuf_alloc()
return new MTLIndexBuf();
};
PixelBuffer *MTLBackend::pixelbuf_alloc(uint size)
{
return new MTLPixelBuffer(size);
};
QueryPool *MTLBackend::querypool_alloc()
{
return new MTLQueryPool();

View File

@@ -538,6 +538,24 @@ bool MTLCommandBufferManager::insert_memory_barrier(eGPUBarrier barrier_bits,
return false;
}
void MTLCommandBufferManager::encode_signal_event(id<MTLEvent> event, uint64_t signal_value)
{
/* Ensure active command buffer. */
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
BLI_assert(cmd_buf);
this->end_active_command_encoder();
[cmd_buf encodeSignalEvent:event value:signal_value];
}
void MTLCommandBufferManager::encode_wait_for_event(id<MTLEvent> event, uint64_t signal_value)
{
/* Ensure active command buffer. */
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
BLI_assert(cmd_buf);
this->end_active_command_encoder();
[cmd_buf encodeWaitForEvent:event value:signal_value];
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -555,6 +555,8 @@ class MTLCommandBufferManager {
bool insert_memory_barrier(eGPUBarrier barrier_bits,
eGPUStageBarrierBits before_stages,
eGPUStageBarrierBits after_stages);
void encode_signal_event(id<MTLEvent> event, uint64_t value);
void encode_wait_for_event(id<MTLEvent> event, uint64_t value);
/* TODO(Metal): Support fences in command buffer class. */
/* Debug. */

View File

@@ -84,4 +84,24 @@ class MTLStateManager : public StateManager {
MEM_CXX_CLASS_ALLOC_FUNCS("MTLStateManager")
};
/* Fence synchronization primitive. */
class MTLFence : public Fence {
private:
/* Using an event in this instance, as this is global for the command stream, rather than being
* inserted at the encoder level. This has the behaviour to match the GL functionality. */
id<MTLEvent> mtl_event_ = nil;
/* Events can be re-used multiple times. We can track a counter flagging the latest value
* signalled. */
uint64_t last_signalled_value_ = 0;
public:
MTLFence() : Fence(){};
~MTLFence();
void signal() override;
void wait() override;
MEM_CXX_CLASS_ALLOC_FUNCS("MTLFence")
};
} // namespace blender::gpu

View File

@@ -560,6 +560,44 @@ void MTLStateManager::issue_barrier(eGPUBarrier barrier_bits)
ctx->main_command_buffer.insert_memory_barrier(barrier_bits, before_stages, after_stages);
}
MTLFence::~MTLFence()
{
if (mtl_event_) {
[mtl_event_ release];
mtl_event_ = nil;
}
}
void MTLFence::signal()
{
if (mtl_event_ == nil) {
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);
mtl_event_ = [ctx->device newEvent];
}
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);
ctx->main_command_buffer.encode_signal_event(mtl_event_, ++last_signalled_value_);
signalled_ = true;
}
void MTLFence::wait()
{
/* do not attempt to wait if event has not yet been signalled for the first time. */
if (mtl_event_ == nil) {
return;
}
if (signalled_) {
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);
ctx->main_command_buffer.encode_wait_for_event(mtl_event_, last_signalled_value_);
signalled_ = false;
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -235,6 +235,10 @@ class MTLTexture : public Texture {
void update_sub(
int mip, int offset[3], int extent[3], eGPUDataFormat type, const void *data) override;
void update_sub(int offset[3],
int extent[3],
eGPUDataFormat format,
GPUPixelBuffer *pixbuf) override;
void generate_mipmap() override;
void copy_to(Texture *dst) override;
@@ -424,6 +428,24 @@ class MTLTexture : public Texture {
MEM_CXX_CLASS_ALLOC_FUNCS("MTLTexture")
};
class MTLPixelBuffer : public PixelBuffer {
private:
id<MTLBuffer> buffer_ = nil;
public:
MTLPixelBuffer(uint size);
~MTLPixelBuffer();
void *map() override;
void unmap() override;
int64_t get_native_handle() override;
uint get_size() override;
id<MTLBuffer> get_metal_buffer();
MEM_CXX_CLASS_ALLOC_FUNCS("MTLPixelBuffer")
};
/* Utility */
MTLPixelFormat gpu_texture_format_to_metal(eGPUTextureFormat tex_format);
int get_mtl_format_bytesize(MTLPixelFormat tex_format);

View File

@@ -885,6 +885,61 @@ void gpu::MTLTexture::update_sub(
}
}
void MTLTexture::update_sub(int offset[3],
int extent[3],
eGPUDataFormat format,
GPUPixelBuffer *pixbuf)
{
/* Update texture from pixel buffer. */
BLI_assert(validate_data_format(format_, format));
BLI_assert(pixbuf != nullptr);
/* Fetch pixel buffer metal buffer. */
MTLPixelBuffer *mtl_pix_buf = static_cast<MTLPixelBuffer *>(unwrap(pixbuf));
id<MTLBuffer> buffer = mtl_pix_buf->get_metal_buffer();
BLI_assert(buffer != nil);
if (buffer == nil) {
return;
}
/* Ensure texture is ready. */
this->ensure_baked();
BLI_assert(texture_ != nil);
/* Calculate dimensions. */
int num_image_channels = to_component_len(format_);
uint bits_per_pixel = num_image_channels * to_bytesize(format);
uint bytes_per_row = bits_per_pixel * extent[0];
uint bytes_per_image = bytes_per_row * extent[1];
/* Currently only required for 2D textures. */
if (type_ == GPU_TEXTURE_2D) {
/* Create blit command encoder to copy data. */
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);
id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
[blit_encoder copyFromBuffer:buffer
sourceOffset:0
sourceBytesPerRow:bytes_per_row
sourceBytesPerImage:bytes_per_image
sourceSize:MTLSizeMake(extent[0], extent[1], 1)
toTexture:texture_
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(offset[0], offset[1], 0)];
if (texture_.storageMode == MTLStorageModeManaged) {
[blit_encoder synchronizeResource:texture_];
}
}
else {
BLI_assert(false);
}
}
void gpu::MTLTexture::ensure_mipmaps(int miplvl)
{
@@ -1797,4 +1852,74 @@ void gpu::MTLTexture::reset()
/** \} */
/* -------------------------------------------------------------------- */
/** \name Pixel Buffer
* \{ */
MTLPixelBuffer::MTLPixelBuffer(uint size) : PixelBuffer(size)
{
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);
/* Ensure buffer satifies the alignment of 256 bytes for copying
* data between buffers and textures. As specified in:
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
BLI_assert(size >= 256);
MTLResourceOptions resource_options = ([ctx->device hasUnifiedMemory]) ?
MTLResourceStorageModeShared :
MTLResourceStorageModeManaged;
buffer_ = [ctx->device newBufferWithLength:size options:resource_options];
BLI_assert(buffer_ != nil);
}
MTLPixelBuffer::~MTLPixelBuffer()
{
if (buffer_) {
[buffer_ release];
buffer_ = nil;
}
}
void *MTLPixelBuffer::map()
{
if (buffer_ == nil) {
return nullptr;
}
return [buffer_ contents];
}
void MTLPixelBuffer::unmap()
{
if (buffer_ == nil) {
return;
}
/* Ensure changes are synchronized. */
if (buffer_.resourceOptions & MTLResourceStorageModeManaged) {
[buffer_ didModifyRange:NSMakeRange(0, size_)];
}
}
int64_t MTLPixelBuffer::get_native_handle()
{
if (buffer_ == nil) {
return 0;
}
return reinterpret_cast<int64_t>(buffer_);
}
uint MTLPixelBuffer::get_size()
{
return size_;
}
id<MTLBuffer> MTLPixelBuffer::get_metal_buffer()
{
return buffer_;
}
/** \} */
} // namespace blender::gpu

View File

@@ -76,6 +76,11 @@ class GLBackend : public GPUBackend {
return new GLDrawList(list_length);
};
Fence *fence_alloc() override
{
return new GLFence();
};
FrameBuffer *framebuffer_alloc(const char *name) override
{
return new GLFrameBuffer(name);
@@ -86,6 +91,11 @@ class GLBackend : public GPUBackend {
return new GLIndexBuf();
};
PixelBuffer *pixelbuf_alloc(uint size) override
{
return new GLPixelBuffer(size);
};
QueryPool *querypool_alloc() override
{
return new GLQueryPool();

View File

@@ -641,6 +641,34 @@ void GLStateManager::issue_barrier(eGPUBarrier barrier_bits)
glMemoryBarrier(to_gl(barrier_bits));
}
GLFence::~GLFence()
{
if (gl_sync_ != 0) {
glDeleteSync(gl_sync_);
gl_sync_ = 0;
}
}
void GLFence::signal()
{
/* If fence is already signalled, create a newly signalled fence primitive. */
if (gl_sync_) {
glDeleteSync(gl_sync_);
}
gl_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
signalled_ = true;
}
void GLFence::wait()
{
/* Do not wait if fence does not yet exist. */
if (gl_sync_ == 0) {
return;
}
glWaitSync(gl_sync_, 0, GL_TIMEOUT_IGNORED);
signalled_ = false;
}
/** \} */
} // namespace blender::gpu

View File

@@ -103,6 +103,21 @@ class GLStateManager : public StateManager {
MEM_CXX_CLASS_ALLOC_FUNCS("GLStateManager")
};
/* Fence synchronization primitive. */
class GLFence : public Fence {
private:
GLsync gl_sync_ = 0;
public:
GLFence() : Fence(){};
~GLFence();
void signal() override;
void wait() override;
MEM_CXX_CLASS_ALLOC_FUNCS("GLFence")
};
static inline GLbitfield to_gl(eGPUBarrier barrier_bits)
{
GLbitfield barrier = 0;

View File

@@ -303,6 +303,42 @@ void GLTexture::update_sub(
has_pixels_ = true;
}
void GLTexture::update_sub(int offset[3],
int extent[3],
eGPUDataFormat format,
GPUPixelBuffer *pixbuf)
{
/* Update texture from pixel buffer. */
BLI_assert(validate_data_format(format_, format));
BLI_assert(pixbuf != nullptr);
const int dimensions = this->dimensions_count();
GLenum gl_format = to_gl_data_format(format_);
GLenum gl_type = to_gl(format);
/* Temporarily Bind texture. */
GLContext::state_manager_active_get()->texture_bind_temp(this);
/* Bind pixel buffer for source data. */
int pix_buf_handle = (int)GPU_pixel_buffer_get_native_handle(pixbuf);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pix_buf_handle);
switch (dimensions) {
default:
case 1:
glTexSubImage1D(target_, 0, offset[0], extent[0], gl_format, gl_type, 0);
break;
case 2:
glTexSubImage2D(target_, 0, UNPACK2(offset), UNPACK2(extent), gl_format, gl_type, 0);
break;
case 3:
glTexSubImage3D(target_, 0, UNPACK3(offset), UNPACK3(extent), gl_format, gl_type, 0);
break;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
/**
* This will create the mipmap images and populate them with filtered data from base level.
*
@@ -739,4 +775,63 @@ uint GLTexture::gl_bindcode_get() const
return tex_id_;
}
/* -------------------------------------------------------------------- */
/** \name Pixel Buffer
* \{ */
GLPixelBuffer::GLPixelBuffer(uint size) : PixelBuffer(size)
{
glGenBuffers(1, &gl_id_);
BLI_assert(gl_id_);
if (!gl_id_) {
return;
}
/* Ensure size is non-zero for pixel buffer backing storage creation. */
size = max_ii(size, 32);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_id_);
glBufferData(GL_PIXEL_UNPACK_BUFFER, size, 0, GL_DYNAMIC_DRAW);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
GLPixelBuffer::~GLPixelBuffer()
{
if (!gl_id_) {
return;
}
glDeleteBuffers(1, &gl_id_);
}
void *GLPixelBuffer::map()
{
if (!gl_id_) {
BLI_assert(false);
return nullptr;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_id_);
void *ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
BLI_assert(ptr);
return ptr;
}
void GLPixelBuffer::unmap()
{
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
int64_t GLPixelBuffer::get_native_handle()
{
return (int64_t)gl_id_;
}
uint GLPixelBuffer::get_size()
{
return size_;
}
/** \} */
} // namespace blender::gpu

View File

@@ -46,6 +46,10 @@ class GLTexture : public Texture {
void update_sub(
int mip, int offset[3], int extent[3], eGPUDataFormat type, const void *data) override;
void update_sub(int offset[3],
int extent[3],
eGPUDataFormat format,
GPUPixelBuffer *pixbuf) override;
/**
* This will create the mipmap images and populate them with filtered data from base level.
@@ -87,6 +91,22 @@ class GLTexture : public Texture {
MEM_CXX_CLASS_ALLOC_FUNCS("GLTexture")
};
class GLPixelBuffer : public PixelBuffer {
private:
GLuint gl_id_ = 0;
public:
GLPixelBuffer(uint size);
~GLPixelBuffer();
void *map() override;
void unmap() override;
int64_t get_native_handle() override;
uint get_size() override;
MEM_CXX_CLASS_ALLOC_FUNCS("GLPixelBuffer")
};
inline GLenum to_gl_internal_format(eGPUTextureFormat format)
{
/* You can add any of the available type to this list
@@ -282,6 +302,8 @@ inline GLenum to_gl(eGPUDataFormat format)
return GL_UNSIGNED_INT_2_10_10_10_REV;
case GPU_DATA_10_11_11_REV:
return GL_UNSIGNED_INT_10F_11F_11F_REV;
case GPU_DATA_HALF_FLOAT:
return GL_HALF_FLOAT;
default:
BLI_assert_msg(0, "Unhandled data format");
return GL_FLOAT;

View File

@@ -18,6 +18,7 @@
struct BakeTargets;
struct BakePixel;
struct Depsgraph;
struct GPUContext;
struct Main;
struct Object;
struct Render;
@@ -158,9 +159,12 @@ typedef struct RenderEngine {
void *update_render_passes_data;
/* GPU context. */
void *gpu_context;
void *wm_gpu_context; /* WindowManager GPU context -> GHOSTContext. */
ThreadMutex gpu_context_mutex;
bool use_drw_render_context;
struct GPUContext *gpu_context;
/* Whether to restore DRWState after RenderEngine display pass. */
bool gpu_restore_context;
} RenderEngine;
RenderEngine *RE_engine_create(RenderEngineType *type);

View File

@@ -34,6 +34,8 @@
#include "DEG_depsgraph_debug.h"
#include "DEG_depsgraph_query.h"
#include "GPU_context.h"
#include "RNA_access.h"
#ifdef WITH_PYTHON
@@ -1285,45 +1287,69 @@ bool RE_engine_gpu_context_create(RenderEngine *engine)
BLI_assert(BLI_thread_is_main());
const bool drw_state = DRW_opengl_context_release();
engine->gpu_context = WM_opengl_context_create();
engine->wm_gpu_context = WM_opengl_context_create();
/* On Windows an old context is restored after creation, and subsequent release of context
* generates a Win32 error. Harmless for users, but annoying to have possible misleading
* error prints in the console. */
#ifndef _WIN32
if (engine->gpu_context) {
WM_opengl_context_release(engine->gpu_context);
if (engine->wm_gpu_context) {
/* Activate new OpenGL Context for GPUContext creation. */
WM_opengl_context_activate(engine->wm_gpu_context);
/* Requires GPUContext for usage of GPU Module for displaying results. */
engine->gpu_context = GPU_context_create(nullptr, engine->wm_gpu_context);
GPU_context_active_set(nullptr);
/* Deactivate newly created OpenGL Context, as it is not needed until
* `RE_engine_gpu_context_enable` is called. */
WM_opengl_context_release(engine->wm_gpu_context);
}
else {
engine->gpu_context = nullptr;
}
#endif
DRW_opengl_context_activate(drw_state);
return engine->gpu_context != nullptr;
return engine->wm_gpu_context != nullptr;
}
void RE_engine_gpu_context_destroy(RenderEngine *engine)
{
if (!engine->gpu_context) {
if (!engine->wm_gpu_context) {
return;
}
const bool drw_state = DRW_opengl_context_release();
WM_opengl_context_activate(engine->gpu_context);
WM_opengl_context_dispose(engine->gpu_context);
WM_opengl_context_activate(engine->wm_gpu_context);
if (engine->gpu_context) {
GPUContext *restore_context = GPU_context_active_get();
GPU_context_active_set(engine->gpu_context);
GPU_context_discard(engine->gpu_context);
if (restore_context != engine->gpu_context) {
GPU_context_active_set(restore_context);
}
engine->gpu_context = nullptr;
}
WM_opengl_context_dispose(engine->wm_gpu_context);
DRW_opengl_context_activate(drw_state);
}
bool RE_engine_gpu_context_enable(RenderEngine *engine)
{
engine->gpu_restore_context = false;
if (engine->use_drw_render_context) {
DRW_render_context_enable(engine->re);
return true;
}
if (engine->gpu_context) {
if (engine->wm_gpu_context) {
BLI_mutex_lock(&engine->gpu_context_mutex);
WM_opengl_context_activate(engine->gpu_context);
/* If a previous OpenGL/GPUContext was active (DST.gpu_context), we should later restore this
* when disabling the RenderEngine context. */
engine->gpu_restore_context = DRW_opengl_context_release();
/* Activate RenderEngine OpenGL and GPU Context. */
WM_opengl_context_activate(engine->wm_gpu_context);
if (engine->gpu_context) {
GPU_context_active_set(engine->gpu_context);
GPU_render_begin();
}
return true;
}
return false;
@@ -1335,8 +1361,14 @@ void RE_engine_gpu_context_disable(RenderEngine *engine)
DRW_render_context_disable(engine->re);
}
else {
if (engine->gpu_context) {
WM_opengl_context_release(engine->gpu_context);
if (engine->wm_gpu_context) {
if (engine->gpu_context) {
GPU_render_end();
GPU_context_active_set(nullptr);
}
WM_opengl_context_release(engine->wm_gpu_context);
/* Restore DRW state context if previously active. */
DRW_opengl_context_activate(engine->gpu_restore_context);
BLI_mutex_unlock(&engine->gpu_context_mutex);
}
}
@@ -1348,7 +1380,7 @@ void RE_engine_gpu_context_lock(RenderEngine *engine)
/* Locking already handled by the draw manager. */
}
else {
if (engine->gpu_context) {
if (engine->wm_gpu_context) {
BLI_mutex_lock(&engine->gpu_context_mutex);
}
}
@@ -1360,7 +1392,7 @@ void RE_engine_gpu_context_unlock(RenderEngine *engine)
/* Locking already handled by the draw manager. */
}
else {
if (engine->gpu_context) {
if (engine->wm_gpu_context) {
BLI_mutex_unlock(&engine->gpu_context_mutex);
}
}