Fix T93350: Cycles renders shows black during rendering huge resolutions

The root of the issue is caused by Cycles ignoring OpenGL limitation on
the maximum resolution of textures: Cycles was allocating texture of the
final render resolution. It was exceeding limitation on certain GPUs and
driver.

The idea is simple: use multiple textures for the display, each of which
will fit into OpenGL limitations.

There is some code which allows the display driver to know when to start
the new tile. Also added some code to allow force graphics interop to be
re-created. The latter one ended up not used in the final version of the
patch, but it might be helpful for other drivers implementation.

The tile size is limited to 8K now as it is the safest size for textures
on many GPUs and OpenGL drivers.

This is an updated fix with a workaround for freezing with the NVIDIA
driver on Linux.

Differential Revision: https://developer.blender.org/D13385
This commit is contained in:
Brecht Van Lommel 2022-01-06 16:41:44 +01:00
parent efe3d60a2c
commit ae28d90578
14 changed files with 625 additions and 312 deletions

@ -802,7 +802,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
name="Tile Size", name="Tile Size",
default=2048, default=2048,
description="", description="",
min=8, max=16384, min=8, max=8192,
) )
# Various fine-tuning debug flags # Various fine-tuning debug flags

@ -272,12 +272,300 @@ uint BlenderDisplaySpaceShader::get_shader_program()
return shader_program_; return shader_program_;
} }
/* --------------------------------------------------------------------
* DrawTile.
*/
/* Higher level representation of a texture from the graphics library. */
class GLTexture {
public:
/* Global counter for all allocated OpenGL textures used by instances of this class. */
static inline std::atomic<int> num_used = 0;
GLTexture() = default;
~GLTexture()
{
assert(gl_id == 0);
}
GLTexture(const GLTexture &other) = delete;
GLTexture &operator=(GLTexture &other) = delete;
GLTexture(GLTexture &&other) noexcept
: gl_id(other.gl_id), width(other.width), height(other.height)
{
other.reset();
}
GLTexture &operator=(GLTexture &&other)
{
if (this == &other) {
return *this;
}
gl_id = other.gl_id;
width = other.width;
height = other.height;
other.reset();
return *this;
}
bool gl_resources_ensure()
{
if (gl_id) {
return true;
}
/* Create texture. */
glGenTextures(1, &gl_id);
if (!gl_id) {
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);
++num_used;
return true;
}
void gl_resources_destroy()
{
if (!gl_id) {
return;
}
glDeleteTextures(1, &gl_id);
reset();
--num_used;
}
/* OpenGL resource IDs of the texture.
*
* NOTE: Allocated on the render engine's context. */
uint gl_id = 0;
/* Dimensions of the texture in pixels. */
int width = 0;
int height = 0;
protected:
void reset()
{
gl_id = 0;
width = 0;
height = 0;
}
};
/* Higher level representation of a Pixel Buffer Object (PBO) from the graphics library. */
class GLPixelBufferObject {
public:
/* Global counter for all allocated OpenGL PBOs used by instances of this class. */
static inline std::atomic<int> num_used = 0;
GLPixelBufferObject() = default;
~GLPixelBufferObject()
{
assert(gl_id == 0);
}
GLPixelBufferObject(const GLPixelBufferObject &other) = delete;
GLPixelBufferObject &operator=(GLPixelBufferObject &other) = delete;
GLPixelBufferObject(GLPixelBufferObject &&other) noexcept
: gl_id(other.gl_id), width(other.width), height(other.height)
{
other.reset();
}
GLPixelBufferObject &operator=(GLPixelBufferObject &&other)
{
if (this == &other) {
return *this;
}
gl_id = other.gl_id;
width = other.width;
height = other.height;
other.reset();
return *this;
}
bool gl_resources_ensure()
{
if (gl_id) {
return true;
}
glGenBuffers(1, &gl_id);
if (!gl_id) {
LOG(ERROR) << "Error creating texture pixel buffer object.";
return false;
}
++num_used;
return true;
}
void gl_resources_destroy()
{
if (!gl_id) {
return;
}
glDeleteBuffers(1, &gl_id);
reset();
--num_used;
}
/* OpenGL resource IDs of the PBO.
*
* NOTE: Allocated on the render engine's context. */
uint gl_id = 0;
/* Dimensions of the PBO. */
int width = 0;
int height = 0;
protected:
void reset()
{
gl_id = 0;
width = 0;
height = 0;
}
};
class DrawTile {
public:
DrawTile() = default;
~DrawTile() = default;
DrawTile(const DrawTile &other) = delete;
DrawTile &operator=(const DrawTile &other) = delete;
DrawTile(DrawTile &&other) noexcept = default;
DrawTile &operator=(DrawTile &&other) = default;
bool gl_resources_ensure()
{
if (!texture.gl_resources_ensure()) {
gl_resources_destroy();
return false;
}
if (!gl_vertex_buffer) {
glGenBuffers(1, &gl_vertex_buffer);
if (!gl_vertex_buffer) {
LOG(ERROR) << "Error allocating tile VBO.";
gl_resources_destroy();
return false;
}
}
return true;
}
void gl_resources_destroy()
{
texture.gl_resources_destroy();
if (gl_vertex_buffer) {
glDeleteBuffers(1, &gl_vertex_buffer);
gl_vertex_buffer = 0;
}
}
inline bool ready_to_draw() const
{
return texture.gl_id != 0;
}
/* Texture which contains pixels of the tile. */
GLTexture texture;
/* Display parameters the texture of this tile has been updated for. */
BlenderDisplayDriver::Params params;
/* OpenGL resources needed for drawing. */
uint gl_vertex_buffer = 0;
};
class DrawTileAndPBO {
public:
bool gl_resources_ensure()
{
if (!tile.gl_resources_ensure() || !buffer_object.gl_resources_ensure()) {
gl_resources_destroy();
return false;
}
return true;
}
void gl_resources_destroy()
{
tile.gl_resources_destroy();
buffer_object.gl_resources_destroy();
}
DrawTile tile;
GLPixelBufferObject buffer_object;
};
/* -------------------------------------------------------------------- /* --------------------------------------------------------------------
* BlenderDisplayDriver. * BlenderDisplayDriver.
*/ */
struct BlenderDisplayDriver::Tiles {
/* Resources of a tile which is being currently rendered. */
DrawTileAndPBO current_tile;
/* All tiles which rendering is finished and which content will not be changed. */
struct {
vector<DrawTile> tiles;
void gl_resources_destroy_and_clear()
{
for (DrawTile &tile : tiles) {
tile.gl_resources_destroy();
}
tiles.clear();
}
} finished_tiles;
};
BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene) BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene)
: b_engine_(b_engine), display_shader_(BlenderDisplayShader::create(b_engine, b_scene)) : b_engine_(b_engine),
display_shader_(BlenderDisplayShader::create(b_engine, b_scene)),
tiles_(make_unique<Tiles>())
{ {
/* Create context while on the main thread. */ /* Create context while on the main thread. */
gl_context_create(); gl_context_create();
@ -292,6 +580,21 @@ BlenderDisplayDriver::~BlenderDisplayDriver()
* Update procedure. * Update procedure.
*/ */
void BlenderDisplayDriver::next_tile_begin()
{
if (!tiles_->current_tile.tile.ready_to_draw()) {
LOG(ERROR)
<< "Unexpectedly moving to the next tile without any data provided for current tile.";
return;
}
/* Moving to the next tile without giving render data for the current tile is not an expected
* situation. */
DCHECK(!need_clear_);
tiles_->finished_tiles.tiles.emplace_back(std::move(tiles_->current_tile.tile));
}
bool BlenderDisplayDriver::update_begin(const Params &params, bool BlenderDisplayDriver::update_begin(const Params &params,
int texture_width, int texture_width,
int texture_height) int texture_height)
@ -312,24 +615,33 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED); glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
} }
if (!gl_texture_resources_ensure()) { DrawTile &current_tile = tiles_->current_tile.tile;
GLPixelBufferObject &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.
* It also makes the logic reliable from the whether drawing did happen or not point of view. */
if (need_clear_) {
tiles_->finished_tiles.gl_resources_destroy_and_clear();
need_clear_ = false;
}
if (!tiles_->current_tile.gl_resources_ensure()) {
tiles_->current_tile.gl_resources_destroy();
gl_context_disable(); gl_context_disable();
return false; return false;
} }
/* Update texture dimensions if needed. */ /* Update texture dimensions if needed. */
if (texture_.width != texture_width || texture_.height != texture_height) { if (current_tile.texture.width != texture_width ||
current_tile.texture.height != texture_height) {
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_.gl_id); glBindTexture(GL_TEXTURE_2D, current_tile.texture.gl_id);
glTexImage2D( glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA16F, texture_width, texture_height, 0, GL_RGBA, GL_HALF_FLOAT, 0); GL_TEXTURE_2D, 0, GL_RGBA16F, texture_width, texture_height, 0, GL_RGBA, GL_HALF_FLOAT, 0);
texture_.width = texture_width; current_tile.texture.width = texture_width;
texture_.height = texture_height; current_tile.texture.height = texture_height;
glBindTexture(GL_TEXTURE_2D, 0); 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. /* Update PBO dimensions if needed.
@ -341,29 +653,58 @@ bool BlenderDisplayDriver::update_begin(const Params &params,
* sending too much data to GPU when resolution divider is not 1. */ * sending too much data to GPU when resolution divider is not 1. */
/* TODO(sergey): Investigate whether keeping the PBO exact size of the texture makes non-interop /* TODO(sergey): Investigate whether keeping the PBO exact size of the texture makes non-interop
* mode faster. */ * mode faster. */
const int buffer_width = params.full_size.x; const int buffer_width = params.size.x;
const int buffer_height = params.full_size.y; const int buffer_height = params.size.y;
if (texture_.buffer_width != buffer_width || texture_.buffer_height != buffer_height) { 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; const size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, current_tile_buffer_object.gl_id);
glBufferData(GL_PIXEL_UNPACK_BUFFER, size_in_bytes, 0, GL_DYNAMIC_DRAW); glBufferData(GL_PIXEL_UNPACK_BUFFER, size_in_bytes, 0, GL_DYNAMIC_DRAW);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
texture_.buffer_width = buffer_width; current_tile_buffer_object.width = buffer_width;
texture_.buffer_height = buffer_height; current_tile_buffer_object.height = buffer_height;
} }
/* New content will be provided to the texture in one way or another, so mark this in a /* Store an updated parameters of the current tile.
* centralized place. */ * In theory it is only needed once per update of the tile, but doing it on every update is
texture_.need_update = true; * the easiest and is not expensive. */
tiles_->current_tile.tile.params = params;
texture_.params = params;
return true; return true;
} }
static void update_tile_texture_pixels(const DrawTileAndPBO &tile)
{
const GLTexture &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);
}
void BlenderDisplayDriver::update_end() void BlenderDisplayDriver::update_end()
{ {
/* Unpack the PBO into the texture as soon as the new content is provided.
*
* This allows to ensure that the unpacking happens while resources like graphics interop (which
* lifetime is outside of control of the display driver) are still valid, as well as allows to
* move the tile from being current to finished immediately after this call.
*
* One concern with this approach is that if the update happens more often than drawing then
* doing the unpack here occupies GPU transfer for no good reason. However, the render scheduler
* takes care of ensuring updates don't happen that often. In regular applications redraw will
* happen much more often than this update. */
update_tile_texture_pixels(tiles_->current_tile);
gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush(); glFlush();
@ -376,7 +717,11 @@ void BlenderDisplayDriver::update_end()
half4 *BlenderDisplayDriver::map_texture_buffer() half4 *BlenderDisplayDriver::map_texture_buffer()
{ {
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); 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 *>( half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(
glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
@ -384,15 +729,6 @@ half4 *BlenderDisplayDriver::map_texture_buffer()
LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object."; LOG(ERROR) << "Error mapping BlenderDisplayDriver 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; return mapped_rgba_pixels;
} }
@ -411,12 +747,9 @@ BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get
{ {
GraphicsInterop interop_dst; GraphicsInterop interop_dst;
interop_dst.buffer_width = texture_.buffer_width; interop_dst.buffer_width = tiles_->current_tile.buffer_object.width;
interop_dst.buffer_height = texture_.buffer_height; interop_dst.buffer_height = tiles_->current_tile.buffer_object.height;
interop_dst.opengl_pbo_id = texture_.gl_pbo_id; interop_dst.opengl_pbo_id = tiles_->current_tile.buffer_object.gl_id;
interop_dst.need_clear = texture_.need_clear;
texture_.need_clear = false;
return interop_dst; return interop_dst;
} }
@ -437,7 +770,7 @@ void BlenderDisplayDriver::graphics_interop_deactivate()
void BlenderDisplayDriver::clear() void BlenderDisplayDriver::clear()
{ {
texture_.need_clear = true; need_clear_ = true;
} }
void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y) void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y)
@ -445,26 +778,155 @@ void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y)
zoom_ = make_float2(zoom_x, zoom_y); zoom_ = make_float2(zoom_x, zoom_y);
} }
/* 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. */
static void vertex_buffer_update(const DisplayDriver::Params &params)
{
const int x = params.full_offset.x;
const int y = params.full_offset.y;
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);
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] = x;
vpointer[3] = y;
vpointer[4] = 1.0f;
vpointer[5] = 0.0f;
vpointer[6] = x + width;
vpointer[7] = y;
vpointer[8] = 1.0f;
vpointer[9] = 1.0f;
vpointer[10] = x + width;
vpointer[11] = y + height;
vpointer[12] = 0.0f;
vpointer[13] = 1.0f;
vpointer[14] = x;
vpointer[15] = y + height;
glUnmapBuffer(GL_ARRAY_BUFFER);
}
static void draw_tile(const float2 &zoom,
const int texcoord_attribute,
const int position_attribute,
const DrawTile &draw_tile)
{
if (!draw_tile.ready_to_draw()) {
return;
}
const GLTexture &texture = draw_tile.texture;
DCHECK_NE(texture.gl_id, 0);
DCHECK_NE(draw_tile.gl_vertex_buffer, 0);
glBindBuffer(GL_ARRAY_BUFFER, draw_tile.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);
/* Trick to keep sharp rendering without jagged edges on all GPUs.
*
* The idea here is to enforce driver to use linear interpolation when the image is not zoomed
* in.
* For the render result with a resolution divider in effect we always use nearest interpolation.
*
* Use explicit MIN assignment to make sure the driver does not have an undefined behavior at
* the zoom level 1. The MAG filter is always NEAREST. */
const float zoomed_width = draw_tile.params.size.x * zoom.x;
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);
}
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);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
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);
}
void BlenderDisplayDriver::flush()
{
/* This is called from the render thread that also calls update_begin/end, right before ending
* the render loop. We wait for any queued PBO and render commands to be done, before destroying
* the render thread and activating the context in the main thread to destroy resources.
*
* If we don't do this, the NVIDIA driver hangs for a few seconds for when ending 3D viewport
* rendering, for unknown reasons. This was found with NVIDIA driver version 470.73 and a Quadro
* RTX 6000 on Linux. */
if (!gl_context_enable()) {
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);
}
gl_context_disable();
}
void BlenderDisplayDriver::draw(const Params &params) void BlenderDisplayDriver::draw(const Params &params)
{ {
/* See do_update_begin() for why no locking is required here. */ /* See do_update_begin() for why no locking is required here. */
const bool transparent = true; // TODO(sergey): Derive this from Film. const bool transparent = true; // TODO(sergey): Derive this from Film.
if (!gl_draw_resources_ensure()) {
return;
}
if (use_gl_context_) { if (use_gl_context_) {
gl_context_mutex_.lock(); gl_context_mutex_.lock();
} }
if (texture_.need_clear) { if (need_clear_) {
/* Texture is requested to be cleared and was not yet cleared. /* Texture is requested to be cleared and was not yet cleared.
* *
* Do early return which should be equivalent of drawing all-zero texture. * Do early return which should be equivalent of drawing all-zero texture.
* Watch out for the lock though so that the clear happening during update is properly * Watch out for the lock though so that the clear happening during update is properly
* synchronized here. */ * synchronized here. */
gl_context_mutex_.unlock(); if (use_gl_context_) {
gl_context_mutex_.unlock();
}
return; return;
} }
@ -477,66 +939,37 @@ void BlenderDisplayDriver::draw(const Params &params)
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} }
display_shader_->bind(params.full_size.x, params.full_size.y);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_.gl_id);
/* Trick to keep sharp rendering without jagged edges on all GPUs. /* NOTE: THe VAO is to be allocated on the drawing context as it is not shared across contects.
* * Simplest is to allocate it on every redraw so that it is possible to destroy it from a
* The idea here is to enforce driver to use linear interpolation when the image is not zoomed * correct context. */
* in.
* For the render result with a resolution divider in effect we always use nearest interpolation.
*
* Use explicit MIN assignment to make sure the driver does not have an undefined behavior at
* the zoom level 1. The MAG filter is always NEAREST. */
const float zoomed_width = params.size.x * zoom_.x;
const float zoomed_height = params.size.y * zoom_.y;
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 if (zoomed_width - params.size.x > 0.5f || zoomed_height - params.size.y > 0.5f) {
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);
/* TODO(sergey): Does it make sense/possible to cache/reuse the VAO? */
GLuint vertex_array_object; GLuint vertex_array_object;
glGenVertexArrays(1, &vertex_array_object); glGenVertexArrays(1, &vertex_array_object);
glBindVertexArray(vertex_array_object); glBindVertexArray(vertex_array_object);
display_shader_->bind(params.full_size.x, params.full_size.y);
const int texcoord_attribute = display_shader_->get_tex_coord_attrib_location(); const int texcoord_attribute = display_shader_->get_tex_coord_attrib_location();
const int position_attribute = display_shader_->get_position_attrib_location(); const int position_attribute = display_shader_->get_position_attrib_location();
glEnableVertexAttribArray(texcoord_attribute); glEnableVertexAttribArray(texcoord_attribute);
glEnableVertexAttribArray(position_attribute); glEnableVertexAttribArray(position_attribute);
glVertexAttribPointer( draw_tile(zoom_, texcoord_attribute, position_attribute, tiles_->current_tile.tile);
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); for (const DrawTile &tile : tiles_->finished_tiles.tiles) {
draw_tile(zoom_, texcoord_attribute, position_attribute, tile);
glBindBuffer(GL_ARRAY_BUFFER, 0); }
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteVertexArrays(1, &vertex_array_object);
display_shader_->unbind(); display_shader_->unbind();
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteVertexArrays(1, &vertex_array_object);
if (transparent) { if (transparent) {
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
@ -544,6 +977,11 @@ void BlenderDisplayDriver::draw(const Params &params)
gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush(); glFlush();
if (VLOG_IS_ON(5)) {
VLOG(5) << "Number of textures: " << GLTexture::num_used;
VLOG(5) << "Number of PBOs: " << GLPixelBufferObject::num_used;
}
if (use_gl_context_) { if (use_gl_context_) {
gl_context_mutex_.unlock(); gl_context_mutex_.unlock();
} }
@ -618,154 +1056,16 @@ void BlenderDisplayDriver::gl_context_dispose()
} }
} }
bool BlenderDisplayDriver::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 BlenderDisplayDriver::gl_resources_destroy() void BlenderDisplayDriver::gl_resources_destroy()
{ {
gl_context_enable(); gl_context_enable();
if (vertex_buffer_ != 0) { tiles_->current_tile.gl_resources_destroy();
glDeleteBuffers(1, &vertex_buffer_); tiles_->finished_tiles.gl_resources_destroy_and_clear();
}
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(); gl_context_disable();
gl_context_dispose(); gl_context_dispose();
} }
bool BlenderDisplayDriver::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 BlenderDisplayDriver::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 BlenderDisplayDriver::vertex_buffer_update(const Params & /*params*/)
{
/* 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. */
const int x = texture_.params.full_offset.x;
const int y = texture_.params.full_offset.y;
const int width = texture_.params.size.x;
const int height = texture_.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);
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] = x;
vpointer[3] = y;
vpointer[4] = 1.0f;
vpointer[5] = 0.0f;
vpointer[6] = x + width;
vpointer[7] = y;
vpointer[8] = 1.0f;
vpointer[9] = 1.0f;
vpointer[10] = x + width;
vpointer[11] = y + height;
vpointer[12] = 0.0f;
vpointer[13] = 1.0f;
vpointer[14] = x;
vpointer[15] = y + height;
glUnmapBuffer(GL_ARRAY_BUFFER);
}
CCL_NAMESPACE_END CCL_NAMESPACE_END

@ -26,6 +26,7 @@
#include "util/thread.h" #include "util/thread.h"
#include "util/unique_ptr.h" #include "util/unique_ptr.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
@ -112,6 +113,8 @@ class BlenderDisplayDriver : public DisplayDriver {
void set_zoom(float zoom_x, float zoom_y); void set_zoom(float zoom_x, float zoom_y);
protected: protected:
virtual void next_tile_begin() override;
virtual bool update_begin(const Params &params, int texture_width, int texture_height) override; virtual bool update_begin(const Params &params, int texture_width, int texture_height) override;
virtual void update_end() override; virtual void update_end() override;
@ -122,33 +125,17 @@ class BlenderDisplayDriver : public DisplayDriver {
virtual void draw(const Params &params) override; virtual void draw(const Params &params) override;
virtual void flush() override;
/* Helper function which allocates new GPU context. */ /* Helper function which allocates new GPU context. */
void gl_context_create(); void gl_context_create();
bool gl_context_enable(); bool gl_context_enable();
void gl_context_disable(); void gl_context_disable();
void gl_context_dispose(); void gl_context_dispose();
/* 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. */ /* Destroy all GPU resources which are being used by this object. */
void gl_resources_destroy(); 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);
BL::RenderEngine b_engine_; BL::RenderEngine b_engine_;
/* OpenGL context which is used the render engine doesn't have its own. */ /* OpenGL context which is used the render engine doesn't have its own. */
@ -159,50 +146,14 @@ class BlenderDisplayDriver : public DisplayDriver {
/* Mutex used to guard the `gl_context_`. */ /* Mutex used to guard the `gl_context_`. */
thread_mutex gl_context_mutex_; thread_mutex gl_context_mutex_;
/* Texture which contains pixels of the render result. */ /* Content of the display is to be filled with zeroes. */
struct { std::atomic<bool> need_clear_ = true;
/* 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;
/* Display parameters the texture has been updated for. */
Params params;
} texture_;
unique_ptr<BlenderDisplayShader> display_shader_; unique_ptr<BlenderDisplayShader> display_shader_;
/* Special track of whether GPU resources were attempted to be created, to avoid attempts of /* Opaque storage for an internal state and data for tiles. */
* their re-creation on failure on every redraw. */ struct Tiles;
bool gl_draw_resource_creation_attempted_ = false; unique_ptr<Tiles> tiles_;
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_render_sync_ = nullptr;
void *gl_upload_sync_ = nullptr; void *gl_upload_sync_ = nullptr;

@ -45,8 +45,10 @@ void CUDADeviceGraphicsInterop::set_display_interop(
need_clear_ = display_interop.need_clear; need_clear_ = display_interop.need_clear;
if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) { if (!display_interop.need_recreate) {
return; if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) {
return;
}
} }
CUDAContextScope scope(device_); CUDAContextScope scope(device_);

@ -115,7 +115,9 @@ bool PathTrace::ready_to_reset()
return false; return false;
} }
void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_tile_params) void PathTrace::reset(const BufferParams &full_params,
const BufferParams &big_tile_params,
const bool reset_rendering)
{ {
if (big_tile_params_.modified(big_tile_params)) { if (big_tile_params_.modified(big_tile_params)) {
big_tile_params_ = big_tile_params; big_tile_params_ = big_tile_params;
@ -128,7 +130,7 @@ void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_t
* It is requires to inform about reset whenever it happens, so that the redraw state tracking is * It is requires to inform about reset whenever it happens, so that the redraw state tracking is
* properly updated. */ * properly updated. */
if (display_) { if (display_) {
display_->reset(full_params); display_->reset(big_tile_params, reset_rendering);
} }
render_state_.has_denoised_result = false; render_state_.has_denoised_result = false;
@ -594,6 +596,15 @@ void PathTrace::draw()
did_draw_after_reset_ |= display_->draw(); did_draw_after_reset_ |= display_->draw();
} }
void PathTrace::flush_display()
{
if (!display_) {
return;
}
display_->flush();
}
void PathTrace::update_display(const RenderWork &render_work) void PathTrace::update_display(const RenderWork &render_work)
{ {
if (!render_work.display.update) { if (!render_work.display.update) {
@ -622,9 +633,8 @@ void PathTrace::update_display(const RenderWork &render_work)
if (display_) { if (display_) {
VLOG(3) << "Perform copy to GPUDisplay work."; VLOG(3) << "Perform copy to GPUDisplay work.";
const int resolution_divider = render_work.resolution_divider; const int texture_width = render_state_.effective_big_tile_params.window_width;
const int texture_width = max(1, full_params_.width / resolution_divider); const int texture_height = render_state_.effective_big_tile_params.window_height;
const int texture_height = max(1, full_params_.height / resolution_divider);
if (!display_->update_begin(texture_width, texture_height)) { if (!display_->update_begin(texture_width, texture_height)) {
LOG(ERROR) << "Error beginning GPUDisplay update."; LOG(ERROR) << "Error beginning GPUDisplay update.";
return; return;

@ -72,7 +72,9 @@ class PathTrace {
* render result. */ * render result. */
bool ready_to_reset(); bool ready_to_reset();
void reset(const BufferParams &full_params, const BufferParams &big_tile_params); void reset(const BufferParams &full_params,
const BufferParams &big_tile_params,
bool reset_rendering);
void device_free(); void device_free();
@ -112,6 +114,9 @@ class PathTrace {
/* Perform drawing of the current state of the DisplayDriver. */ /* Perform drawing of the current state of the DisplayDriver. */
void draw(); void draw();
/* Flush outstanding display commands before ending the render loop. */
void flush_display();
/* Cancel rendering process as soon as possible, without waiting for full tile to be sampled. /* Cancel rendering process as soon as possible, without waiting for full tile to be sampled.
* Used in cases like reset of render session. * Used in cases like reset of render session.
* *

@ -26,15 +26,20 @@ PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(m
{ {
} }
void PathTraceDisplay::reset(const BufferParams &buffer_params) void PathTraceDisplay::reset(const BufferParams &buffer_params, const bool reset_rendering)
{ {
thread_scoped_lock lock(mutex_); thread_scoped_lock lock(mutex_);
params_.full_offset = make_int2(buffer_params.full_x, buffer_params.full_y); params_.full_offset = make_int2(buffer_params.full_x + buffer_params.window_x,
buffer_params.full_y + buffer_params.window_y);
params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height); params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height);
params_.size = make_int2(buffer_params.width, buffer_params.height); params_.size = make_int2(buffer_params.window_width, buffer_params.window_height);
texture_state_.is_outdated = true; texture_state_.is_outdated = true;
if (!reset_rendering) {
driver_->next_tile_begin();
}
} }
void PathTraceDisplay::mark_texture_updated() void PathTraceDisplay::mark_texture_updated()
@ -248,4 +253,9 @@ bool PathTraceDisplay::draw()
return !is_outdated; return !is_outdated;
} }
void PathTraceDisplay::flush()
{
driver_->flush();
}
CCL_NAMESPACE_END CCL_NAMESPACE_END

@ -38,14 +38,17 @@ class BufferParams;
class PathTraceDisplay { class PathTraceDisplay {
public: public:
PathTraceDisplay(unique_ptr<DisplayDriver> driver); explicit PathTraceDisplay(unique_ptr<DisplayDriver> driver);
virtual ~PathTraceDisplay() = default; virtual ~PathTraceDisplay() = default;
/* Reset the display for the new state of render session. Is called whenever session is reset, /* Reset the display for the new state of render session. Is called whenever session is reset,
* which happens on changes like viewport navigation or viewport dimension change. * which happens on changes like viewport navigation or viewport dimension change.
* *
* This call will configure parameters for a changed buffer and reset the texture state. */ * This call will configure parameters for a changed buffer and reset the texture state.
void reset(const BufferParams &buffer_params); *
* When the `reset_rendering` a complete displat reset happens. When it is false reset happens
* for a new state of the buffer parameters which is assumed to correspond to the next tile. */
void reset(const BufferParams &buffer_params, bool reset_rendering);
/* -------------------------------------------------------------------- /* --------------------------------------------------------------------
* Update procedure. * Update procedure.
@ -151,6 +154,9 @@ class PathTraceDisplay {
* Returns true if this call did draw an updated state of the texture. */ * Returns true if this call did draw an updated state of the texture. */
bool draw(); bool draw();
/* Flush outstanding display commands before ending the render loop. */
void flush();
private: private:
/* Display driver implemented by the host application. */ /* Display driver implemented by the host application. */
unique_ptr<DisplayDriver> driver_; unique_ptr<DisplayDriver> driver_;

@ -194,10 +194,10 @@ PassAccessor::Destination PathTraceWork::get_display_destination_template(
PassAccessor::Destination destination(film_->get_display_pass()); PassAccessor::Destination destination(film_->get_display_pass());
const int2 display_texture_size = display->get_texture_size(); const int2 display_texture_size = display->get_texture_size();
const int texture_x = effective_buffer_params_.full_x - effective_full_params_.full_x + const int texture_x = effective_buffer_params_.full_x - effective_big_tile_params_.full_x +
effective_buffer_params_.window_x; effective_buffer_params_.window_x - effective_big_tile_params_.window_x;
const int texture_y = effective_buffer_params_.full_y - effective_full_params_.full_y + const int texture_y = effective_buffer_params_.full_y - effective_big_tile_params_.full_y +
effective_buffer_params_.window_y; effective_buffer_params_.window_y - effective_big_tile_params_.window_y;
destination.offset = texture_y * display_texture_size.x + texture_x; destination.offset = texture_y * display_texture_size.x + texture_x;
destination.stride = display_texture_size.x; destination.stride = display_texture_size.x;

@ -875,8 +875,10 @@ void PathTraceWorkGPU::copy_to_display_naive(PathTraceDisplay *display,
const int final_width = buffers_->params.window_width; const int final_width = buffers_->params.window_width;
const int final_height = buffers_->params.window_height; const int final_height = buffers_->params.window_height;
const int texture_x = full_x - effective_full_params_.full_x + effective_buffer_params_.window_x; const int texture_x = full_x - effective_big_tile_params_.full_x +
const int texture_y = full_y - effective_full_params_.full_y + effective_buffer_params_.window_y; effective_buffer_params_.window_x - effective_big_tile_params_.window_x;
const int texture_y = full_y - effective_big_tile_params_.full_y +
effective_buffer_params_.window_y - effective_big_tile_params_.window_y;
/* Re-allocate display memory if needed, and make sure the device pointer is allocated. /* Re-allocate display memory if needed, and make sure the device pointer is allocated.
* *

@ -54,6 +54,8 @@ class DisplayDriver {
} }
}; };
virtual void next_tile_begin() = 0;
/* Update the render from the rendering thread. /* Update the render from the rendering thread.
* *
* Cycles periodically updates the render to be displayed. For multithreaded updates with * Cycles periodically updates the render to be displayed. For multithreaded updates with
@ -80,6 +82,9 @@ class DisplayDriver {
virtual bool update_begin(const Params &params, int width, int height) = 0; virtual bool update_begin(const Params &params, int width, int height) = 0;
virtual void update_end() = 0; virtual void update_end() = 0;
/* Optionally flush outstanding display commands before ending the render loop. */
virtual void flush(){};
virtual half4 *map_texture_buffer() = 0; virtual half4 *map_texture_buffer() = 0;
virtual void unmap_texture_buffer() = 0; virtual void unmap_texture_buffer() = 0;
@ -97,6 +102,17 @@ class DisplayDriver {
/* Clear the entire buffer before doing partial write to it. */ /* Clear the entire buffer before doing partial write to it. */
bool need_clear = false; bool need_clear = false;
/* Enforce re-creation of the graphics interop object.
*
* When this field is true then the graphics interop will be re-created no matter what the
* rest of the configuration is.
* When this field is false the graphics interop will be re-created if the PBO or buffer size
* did change.
*
* This allows to ensure graphics interop is re-created when there is a possibility that an
* underlying PBO was re-allocated but did not change its ID. */
bool need_recreate = false;
}; };
virtual GraphicsInterop graphics_interop_get() virtual GraphicsInterop graphics_interop_get()

@ -192,6 +192,8 @@ void Session::run_main_render_loop()
break; break;
} }
} }
path_trace_->flush_display();
} }
void Session::run() void Session::run()
@ -303,7 +305,7 @@ RenderWork Session::run_update_for_next_iteration()
tile_params.update_offset_stride(); tile_params.update_offset_stride();
path_trace_->reset(buffer_params_, tile_params); path_trace_->reset(buffer_params_, tile_params, did_reset);
} }
const int resolution = render_work.resolution_divider; const int resolution = render_work.resolution_divider;
@ -384,7 +386,8 @@ int2 Session::get_effective_tile_size() const
const int tile_size = tile_manager_.compute_render_tile_size(params.tile_size); const int tile_size = tile_manager_.compute_render_tile_size(params.tile_size);
const int64_t actual_tile_area = static_cast<int64_t>(tile_size) * tile_size; const int64_t actual_tile_area = static_cast<int64_t>(tile_size) * tile_size;
if (actual_tile_area >= image_area) { if (actual_tile_area >= image_area && image_width <= TileManager::MAX_TILE_SIZE &&
image_height <= TileManager::MAX_TILE_SIZE) {
return make_int2(image_width, image_height); return make_int2(image_width, image_height);
} }

@ -341,8 +341,10 @@ int TileManager::compute_render_tile_size(const int suggested_tile_size) const
/* Must be a multiple of IMAGE_TILE_SIZE so that we can write render tiles into the image file /* Must be a multiple of IMAGE_TILE_SIZE so that we can write render tiles into the image file
* aligned on image tile boundaries. We can't set IMAGE_TILE_SIZE equal to the render tile size * aligned on image tile boundaries. We can't set IMAGE_TILE_SIZE equal to the render tile size
* because too big tile size leads to integer overflow inside OpenEXR. */ * because too big tile size leads to integer overflow inside OpenEXR. */
return (suggested_tile_size <= IMAGE_TILE_SIZE) ? suggested_tile_size : const int computed_tile_size = (suggested_tile_size <= IMAGE_TILE_SIZE) ?
align_up(suggested_tile_size, IMAGE_TILE_SIZE); suggested_tile_size :
align_up(suggested_tile_size, IMAGE_TILE_SIZE);
return min(computed_tile_size, MAX_TILE_SIZE);
} }
void TileManager::reset_scheduling(const BufferParams &params, int2 tile_size) void TileManager::reset_scheduling(const BufferParams &params, int2 tile_size)

@ -122,6 +122,12 @@ class TileManager {
/* Tile size in the image file. */ /* Tile size in the image file. */
static const int IMAGE_TILE_SIZE = 128; static const int IMAGE_TILE_SIZE = 128;
/* Maximum supported tile size.
* Needs to be safe from allocation on a GPU point of view: the display driver needs to be able
* to allocate texture with the side size of this value.
* Use conservative value which is safe for most of OpenGL drivers and GPUs. */
static const int MAX_TILE_SIZE = 8192;
protected: protected:
/* Get tile configuration for its index. /* Get tile configuration for its index.
* The tile index must be within [0, state_.tile_state_). */ * The tile index must be within [0, state_.tile_state_). */