Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,823 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <string>
#include "BKE_global.hh"
#if defined(WIN32)
# include "BLI_winstuff.h"
#endif
#include "BLI_array.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_subprocess.hh"
#include "BLI_threads.h"
#include "BLI_vector.hh"
#include "CLG_log.h"
#include "DNA_userdef_types.h"
#include "gpu_capabilities_private.hh"
#include "gpu_platform_private.hh"
#include "gl_debug.hh"
#include "gl_backend.hh"
namespace blender {
static CLG_LogRef LOG = {"gpu.opengl"};
namespace gpu {
/* -------------------------------------------------------------------- */
/** \name Platform
* \{ */
static bool match_renderer(StringRef renderer, const Vector<std::string> &items)
{
for (const std::string &item : items) {
const std::string wrapped = " " + item + " ";
if (renderer.endswith(item) || renderer.find(wrapped) != StringRef::not_found) {
return true;
}
}
return false;
}
static bool parse_version(const std::string &version,
const std::string &format,
Vector<int> &r_version)
{
int f = 0;
std::string subversion;
for (int v : IndexRange(version.size())) {
bool match = false;
if (format[f] == '0') {
if (std::isdigit(version[v])) {
match = true;
subversion.push_back(version[v]);
}
}
else {
match = version[v] == format[f];
if (!subversion.empty()) {
r_version.append(std::stoi(subversion));
subversion.clear();
}
}
if (!match) {
f = 0;
subversion.clear();
r_version.clear();
continue;
}
f++;
if (f == format.size()) {
return true;
}
}
return false;
}
/** Try to check if the driver is older than 22.6.1, preferring false positives. */
static bool is_bad_AMD_driver(const char *version_cstr)
{
std::string version_str = version_cstr;
/* Allow matches when the version number is at the string end. */
version_str.push_back(' ');
Vector<int> version;
if (parse_version(version_str, " 00.00.00.00 ", version) ||
parse_version(version_str, " 00.00.0.000000 ", version) ||
parse_version(version_str, " 00.00.00.000000 ", version) ||
parse_version(version_str, " 00.00.000000 ", version) ||
parse_version(version_str, " 00.00.00 ", version) ||
parse_version(version_str, " 00.00.0 ", version) ||
parse_version(version_str, " 00.0.00 ", version) ||
parse_version(version_str, " 00.Q0.", version))
{
return version[0] < 23;
}
/* Some drivers only expose the Windows version https://gpuopen.com/version-table/ */
if (parse_version(version_str, " 00.00.00000.00000 ", version) ||
parse_version(version_str, " 00.00.00000.0000 ", version) ||
parse_version(version_str, " 00.00.0000.00000 ", version))
{
return version[0] < 31 || (version[0] == 31 && version[2] < 21001);
}
/* Unknown version, assume it's a bad one. */
return true;
}
void GLBackend::platform_init()
{
BLI_assert(!GPG.initialized);
const char *vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
const char *renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
const char *version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
GPUDeviceType device = GPU_DEVICE_ANY;
GPUOSType os = GPU_OS_ANY;
GPUDriverType driver = GPU_DRIVER_ANY;
GPUSupportLevel support_level = GPU_SUPPORT_LEVEL_SUPPORTED;
#ifdef _WIN32
os = GPU_OS_WIN;
#else
os = GPU_OS_UNIX;
#endif
if (!vendor) {
printf("Warning: No OpenGL vendor detected.\n");
device = GPU_DEVICE_UNKNOWN;
driver = GPU_DRIVER_ANY;
}
else if (strstr(renderer, "Mesa DRI R") ||
(strstr(renderer, "Radeon") && (strstr(vendor, "X.Org") || strstr(version, "Mesa"))) ||
(strstr(renderer, "AMD") && (strstr(vendor, "X.Org") || strstr(version, "Mesa"))) ||
(strstr(renderer, "Gallium ") && strstr(renderer, " on ATI ")) ||
(strstr(renderer, "Gallium ") && strstr(renderer, " on AMD ")))
{
device = GPU_DEVICE_ATI;
driver = GPU_DRIVER_OPENSOURCE;
}
else if (strstr(vendor, "ATI") || strstr(vendor, "AMD")) {
device = GPU_DEVICE_ATI;
driver = GPU_DRIVER_OFFICIAL;
}
else if (strstr(vendor, "NVIDIA")) {
device = GPU_DEVICE_NVIDIA;
driver = GPU_DRIVER_OFFICIAL;
}
else if (strstr(vendor, "Intel") ||
/* src/mesa/drivers/dri/intel/intel_context.c */
strstr(renderer, "Mesa DRI Intel") || strstr(renderer, "Mesa DRI Mobile Intel"))
{
device = GPU_DEVICE_INTEL;
driver = GPU_DRIVER_OFFICIAL;
if (strstr(renderer, "UHD Graphics") ||
/* Not UHD but affected by the same bugs. */
strstr(renderer, "HD Graphics 530") || strstr(renderer, "Kaby Lake GT2") ||
strstr(renderer, "Whiskey Lake"))
{
device |= GPU_DEVICE_INTEL_UHD;
}
}
else if (strstr(renderer, "Nouveau") || strstr(vendor, "nouveau")) {
device = GPU_DEVICE_NVIDIA;
driver = GPU_DRIVER_OPENSOURCE;
}
else if (strstr(vendor, "Mesa")) {
device = GPU_DEVICE_SOFTWARE;
driver = GPU_DRIVER_SOFTWARE;
}
else if (strstr(vendor, "Microsoft")) {
/* Qualcomm devices use Mesa's GLOn12, which claims to be vended by Microsoft */
if (strstr(renderer, "Qualcomm")) {
device = GPU_DEVICE_QUALCOMM;
driver = GPU_DRIVER_OFFICIAL;
}
else {
device = GPU_DEVICE_SOFTWARE;
driver = GPU_DRIVER_SOFTWARE;
}
}
else if (strstr(vendor, "Apple")) {
/* Apple Silicon. */
device = GPU_DEVICE_APPLE;
driver = GPU_DRIVER_OFFICIAL;
}
else if (strstr(renderer, "Apple Software Renderer")) {
device = GPU_DEVICE_SOFTWARE;
driver = GPU_DRIVER_SOFTWARE;
}
else if (strstr(renderer, "llvmpipe") || strstr(renderer, "softpipe")) {
device = GPU_DEVICE_SOFTWARE;
driver = GPU_DRIVER_SOFTWARE;
}
else {
printf("Warning: Could not find a matching GPU name. Things may not behave as expected.\n");
printf("Detected OpenGL configuration:\n");
printf("Vendor: %s\n", vendor);
printf("Renderer: %s\n", renderer);
}
/* Detect support level */
if (!(epoxy_gl_version() >= 43)) {
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
else {
#if defined(WIN32)
long long driverVersion = 0;
if (device & GPU_DEVICE_QUALCOMM) {
if (BLI_windows_get_directx_driver_version(L"Qualcomm(R) Adreno(TM)", &driverVersion)) {
/* Parse out the driver version in format x.x.x.x */
WORD ver0 = (driverVersion >> 48) & 0xffff;
WORD ver1 = (driverVersion >> 32) & 0xffff;
WORD ver2 = (driverVersion >> 16) & 0xffff;
/* Any Qualcomm driver older than 30.x.x.x will never capable of running blender >= 4.0
* As due to an issue in D3D typed UAV load capabilities, Compute Shaders are not available
* 30.0.3820.x and above are capable of running blender >=4.0, but these drivers
* are only available on 8cx gen3 devices or newer */
if (ver0 < 30 || (ver0 == 30 && ver1 == 0 && ver2 < 3820)) {
std::cout
<< "=====================================\n"
<< "Qualcomm drivers older than 30.0.3820.x cannot run Blender 4.0 or later.\n"
<< "If your device is older than an 8cx Gen3, you must use a 3.x LTS release.\n"
<< "If you have an 8cx Gen3 or newer device, a driver update may be available.\n"
<< "=====================================\n";
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
}
}
#endif
if ((device & GPU_DEVICE_INTEL) && (os & GPU_OS_WIN)) {
/* Old Intel drivers with known bugs that cause material properties to crash.
* Version Build 10.18.14.5067 is the latest available and appears to be working
* ok with our workarounds, so excluded from this list. */
if (strstr(version, "Build 7.14") || strstr(version, "Build 7.15") ||
strstr(version, "Build 8.15") || strstr(version, "Build 9.17") ||
strstr(version, "Build 9.18") || strstr(version, "Build 10.18.10.3") ||
strstr(version, "Build 10.18.10.4") || strstr(version, "Build 10.18.10.5") ||
strstr(version, "Build 10.18.14.4"))
{
support_level = GPU_SUPPORT_LEVEL_LIMITED;
}
/* A rare GPU that has z-fighting issues in edit mode. (see #128179) */
if (strstr(renderer, "HD Graphics 405")) {
support_level = GPU_SUPPORT_LEVEL_LIMITED;
}
/* Latest Intel driver have bugs that won't allow Blender to start.
* Users must install different version of the driver.
* See #113124 for more information. */
if (strstr(version, "Build 20.19.15.51")) {
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
}
if ((device & GPU_DEVICE_ATI) && (os & GPU_OS_UNIX)) {
/* Platform seems to work when SB backend is disabled. This can be done
* by adding the environment variable `R600_DEBUG=nosb`. */
if (strstr(renderer, "AMD CEDAR")) {
support_level = GPU_SUPPORT_LEVEL_LIMITED;
}
}
if ((device & GPU_DEVICE_QUALCOMM) && (os & GPU_OS_WIN)) {
if (strstr(version, "Mesa 20.") || strstr(version, "Mesa 21.") ||
strstr(version, "Mesa 22.") || strstr(version, "Mesa 23."))
{
std::cerr << "Unsupported driver. Requires at least Mesa 24.0.0." << std::endl;
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
}
/* Check SSBO bindings requirement. */
GLint max_ssbo_binds_vertex;
GLint max_ssbo_binds_fragment;
GLint max_ssbo_binds_compute;
glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &max_ssbo_binds_vertex);
glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &max_ssbo_binds_fragment);
glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &max_ssbo_binds_compute);
GLint max_ssbo_binds = std::min(
{max_ssbo_binds_vertex, max_ssbo_binds_fragment, max_ssbo_binds_compute});
if (max_ssbo_binds < 12) {
std::cout << "Warning: Unsupported platform as it supports max " << max_ssbo_binds
<< " SSBO binding locations\n";
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
if (!epoxy_has_gl_extension("GL_ARB_shader_draw_parameters")) {
std::cout << "Error: The OpenGL implementation doesn't support ARB_shader_draw_parameters\n";
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
if (!epoxy_has_gl_extension("GL_ARB_clip_control")) {
std::cout << "Error: The OpenGL implementation doesn't support ARB_clip_control\n";
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
}
/* Compute shaders have some issues with those versions (see #94936). */
if ((device & GPU_DEVICE_ATI) && (driver & GPU_DRIVER_OFFICIAL) &&
(strstr(version, "4.5.14831") || strstr(version, "4.5.14760")))
{
support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED;
}
GPG.init(device,
os,
driver,
support_level,
GPU_BACKEND_OPENGL,
vendor,
renderer,
version,
GPU_ARCHITECTURE_IMR);
GPG.devices.append(
{.identifier = "OPENGL", .index = 0, .vendor_id = 0, .device_id = 0, .name = renderer});
GPG.device_uuid.reinitialize(0);
GPG.device_luid.reinitialize(0);
GPG.device_luid_node_mask = 0;
if (epoxy_has_gl_extension("GL_EXT_memory_object")) {
GLint number_of_devices = 0;
glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &number_of_devices);
/* Multiple devices could be used by the context if certain extensions like multi-cast is used.
* But this is not used by Blender, so this should always be 1. */
BLI_assert(number_of_devices == 1);
GLubyte device_uuid[GL_UUID_SIZE_EXT] = {0};
glGetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, 0, device_uuid);
GPG.device_uuid = Array<uint8_t, 16>(Span<uint8_t>(device_uuid, GL_UUID_SIZE_EXT));
/* LUID is only supported on Windows. */
if (epoxy_has_gl_extension("GL_EXT_memory_object_win32") && (os & GPU_OS_WIN)) {
GLubyte device_luid[GL_LUID_SIZE_EXT] = {0};
glGetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, device_luid);
GPG.device_luid = Array<uint8_t, 8>(Span<uint8_t>(device_luid, GL_LUID_SIZE_EXT));
GLint node_mask = 0;
glGetIntegerv(GL_DEVICE_NODE_MASK_EXT, &node_mask);
GPG.device_luid_node_mask = uint32_t(node_mask);
}
}
}
void GLBackend::platform_exit()
{
BLI_assert(GPG.initialized);
GPG.clear();
}
TexturePool *GLBackend::texturepool_alloc()
{
if (GCaps.texture_pool_workaround) {
CLOG_TRACE(&LOG, "Using texture pool \"TexturePoolImpl\".");
return new TexturePoolImpl();
}
CLOG_TRACE(&LOG, "Using texture pool \"GLTexturePool\".");
return new GLTexturePool();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capabilities
* \{ */
static const char *gl_extension_get(int i)
{
return reinterpret_cast<char *>(const_cast<GLubyte *>(glGetStringi(GL_EXTENSIONS, i)));
}
static void detect_workarounds()
{
const char *vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
const char *renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
const char *version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
if (G.debug & G_DEBUG_GPU_FORCE_WORKAROUNDS) {
printf("\n");
printf("GL: Forcing workaround usage and disabling extensions.\n");
printf(" OpenGL identification strings\n");
printf(" vendor: %s\n", vendor);
printf(" renderer: %s\n", renderer);
printf(" version: %s\n\n", version);
GCaps.depth_blitting_workaround = true;
GCaps.stencil_clasify_buffer_workaround = true;
GCaps.texture_pool_workaround = true;
GLContext::debug_layer_workaround = true;
/* Turn off Blender features. */
GCaps.hdr_viewport_support = false;
/* Turn off OpenGL 4.4 features. */
GLContext::multi_bind_support = false;
GLContext::multi_bind_image_support = false;
/* Turn off OpenGL 4.5 features. */
GLContext::direct_state_access_support = false;
GLContext::derivative_control_support = false;
/* Turn off OpenGL 4.6 features. */
GLContext::texture_filter_anisotropic_support = false;
/* Turn off extensions. */
GLContext::layered_rendering_support = false;
GLContext::vertex_shader_viewport_index_support = false;
GLContext::vertex_shader_layer_support = false;
/* Turn off vendor specific extensions. */
GLContext::native_barycentric_support = false;
GLContext::framebuffer_fetch_support = false;
GLContext::texture_barrier_support = false;
GCaps.stencil_export_support = false;
#if 0
/* Do not alter OpenGL 4.3 features.
* These code paths should be removed. */
GLContext::debug_layer_support = false;
#endif
return;
}
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_WIN, GPU_DRIVER_OFFICIAL) &&
(strstr(version, "4.5.13399") || strstr(version, "4.5.13417") ||
strstr(version, "4.5.13422") || strstr(version, "4.5.13467")))
{
/* The renderers include:
* Radeon HD 5000;
* Radeon HD 7500M;
* Radeon HD 7570M;
* Radeon HD 7600M;
* Radeon R5 Graphics;
* And others... */
GLContext::unused_fb_slot_workaround = true;
}
/* We have issues with this specific renderer. (see #74024) */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE) &&
(strstr(renderer, "AMD VERDE") || strstr(renderer, "AMD KAVERI") ||
strstr(renderer, "AMD TAHITI")))
{
GLContext::unused_fb_slot_workaround = true;
}
/* See #82856: AMD drivers since 20.11 running on a polaris architecture doesn't support the
* `GL_INT_2_10_10_10_REV` data type correctly. This data type is used to pack normals and flags.
* The work around uses `TextureFormat::SINT_16_16_16_16`. In 22.?.? drivers this
* has been fixed for polaris platform. Keeping legacy platforms around just in case.
*/
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
/* Check for AMD legacy driver. Assuming that when these drivers are used this bug is present.
*/
if (is_bad_AMD_driver(version)) {
GCaps.use_hq_normals_workaround = true;
}
const Vector<std::string> matches = {
"RX550/550", "(TM) 520", "(TM) 530", "(TM) 535", "R5", "R7", "R9", "HD"};
if (match_renderer(renderer, matches)) {
GCaps.use_hq_normals_workaround = true;
}
}
/* Maybe not all of these drivers have problems with `GL_ARB_base_instance`.
* But it's hard to test each case.
* We get crashes from some crappy Intel drivers don't work well with shaders created in
* different rendering contexts. */
if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY) &&
(strstr(version, "Build 10.18.10.3") || strstr(version, "Build 10.18.10.4") ||
strstr(version, "Build 10.18.10.5") || strstr(version, "Build 10.18.14.4") ||
strstr(version, "Build 10.18.14.5")))
{
GCaps.use_main_context_workaround = true;
}
/* Somehow fixes armature display issues (see #69743). */
if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY) &&
strstr(version, "Build 20.19.15.4285"))
{
GCaps.use_main_context_workaround = true;
}
/* Needed to avoid driver hangs on legacy AMD drivers (see #139939). */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) &&
is_bad_AMD_driver(version))
{
GCaps.use_main_context_workaround = true;
}
/* See #70187: merging vertices fail. This has been tested from `18.2.2` till `19.3.0~dev`
* of the Mesa driver */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE) &&
(strstr(version, "Mesa 18.") || strstr(version, "Mesa 19.0") ||
strstr(version, "Mesa 19.1") || strstr(version, "Mesa 19.2")))
{
GLContext::unused_fb_slot_workaround = true;
}
/* Snapdragon X Elite devices currently have a driver bug that results in
* eevee rendering a black cube with anything except an emission shader
* if shader draw parameters are enabled (#122837) */
#if defined(WIN32)
long long driverVersion = 0;
if (GPU_type_matches(GPU_DEVICE_QUALCOMM, GPU_OS_WIN, GPU_DRIVER_ANY)) {
if (BLI_windows_get_directx_driver_version(L"Qualcomm(R) Adreno(TM)", &driverVersion)) {
/* Parse out the driver version */
WORD ver0 = (driverVersion >> 48) & 0xffff;
/* X Elite devices have GPU driver version 31, and currently no known release version of the
* GPU driver renders the cube correctly. This will be changed when a working driver version
* is released to commercial devices to only enable this flags on older drivers. */
if (ver0 == 31) {
GCaps.stencil_clasify_buffer_workaround = true;
}
/* Disable OpenGL texture pool on Snapdragon 8cx Gen 3 devices. See #142229. We assume that
* these devices use driver 30.x.x.x */
if (ver0 == 30) {
GCaps.texture_pool_workaround = true;
}
}
}
#endif
/* Enable our own incomplete debug layer if no other is available. */
if (GLContext::debug_layer_support == false) {
GLContext::debug_layer_workaround = true;
}
/* There is an issue in AMD official driver where we cannot use multi bind when using images. AMD
* is aware of the issue, but hasn't released a fix. */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
GLContext::multi_bind_image_support = false;
}
/* #107642, #120273 Windows Intel iGPU (multiple generations) incorrectly report that
* they support image binding. But when used it results into `GL_INVALID_OPERATION` with
* `internal format of texture N is not supported`. */
if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_OFFICIAL)) {
GLContext::multi_bind_image_support = false;
}
if (G.debug & G_DEBUG_GPU_NO_TEXTURE_POOL) {
GCaps.texture_pool_workaround = true;
}
/* Disable texture pool on any Intel driver; glTextureView is inconsistently
* broken on Intel HD and newer integrated cards, and output of the vendor string doesn't
* differentiate e.g. an Arc V140 from an Arc B750 :( */
if ((GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_ANY, GPU_DRIVER_ANY) ||
GPU_type_matches(GPU_DEVICE_INTEL_UHD, GPU_OS_ANY, GPU_DRIVER_ANY)))
{
GCaps.texture_pool_workaround = true;
}
/* Disable texture pool on closed source AMD driver; glTextureView
* breaks frame-buffers for several formats. This is not an issue on Mesa. */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
GCaps.texture_pool_workaround = true;
}
/* Metal-related Workarounds. */
/* Minimum Per-Vertex stride is 1 byte for OpenGL. */
GCaps.minimum_per_vertex_stride = 1;
}
/** Internal capabilities. */
GLint GLContext::max_cubemap_size = 0;
GLint GLContext::max_ubo_binds = 0;
GLint GLContext::max_ssbo_binds = 0;
/** Extensions. */
bool GLContext::debug_layer_support = false;
bool GLContext::direct_state_access_support = false;
bool GLContext::explicit_location_support = false;
bool GLContext::framebuffer_fetch_support = false;
bool GLContext::layered_rendering_support = false;
bool GLContext::vertex_shader_viewport_index_support = false;
bool GLContext::vertex_shader_layer_support = false;
bool GLContext::native_barycentric_support = false;
bool GLContext::multi_bind_support = false;
bool GLContext::multi_bind_image_support = false;
bool GLContext::stencil_texturing_support = false;
bool GLContext::texture_barrier_support = false;
bool GLContext::texture_filter_anisotropic_support = false;
bool GLContext::derivative_control_support = false;
/** Workarounds. */
bool GLContext::debug_layer_workaround = false;
bool GLContext::unused_fb_slot_workaround = false;
bool GLContext::generate_mipmap_workaround = false;
void GLBackend::capabilities_init()
{
BLI_assert(epoxy_gl_version() >= 33);
/* Common Capabilities. */
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &GCaps.max_texture_size);
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &GCaps.max_texture_layers);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &GCaps.max_textures);
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &GCaps.max_uniforms_vert);
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &GCaps.max_uniforms_frag);
glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &GCaps.max_batch_indices);
glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &GCaps.max_batch_vertices);
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &GCaps.max_vertex_attribs);
glGetIntegerv(GL_MAX_VARYING_FLOATS, &GCaps.max_varying_floats);
glGetIntegerv(GL_MAX_IMAGE_UNITS, &GCaps.max_images);
glGetIntegerv(GL_NUM_EXTENSIONS, &GCaps.extensions_len);
GCaps.extension_get = gl_extension_get;
GCaps.mem_stats_support = epoxy_has_gl_extension("GL_NVX_gpu_memory_info") ||
epoxy_has_gl_extension("GL_ATI_meminfo");
GCaps.geometry_shader_support = true;
GCaps.hdr_viewport_support = false;
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &GCaps.max_work_group_count[0]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &GCaps.max_work_group_count[1]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &GCaps.max_work_group_count[2]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, &GCaps.max_work_group_size[0]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, &GCaps.max_work_group_size[1]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &GCaps.max_work_group_size[2]);
glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &GCaps.max_shader_storage_buffer_bindings);
glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &GCaps.max_compute_shader_storage_blocks);
int64_t max_ssbo_size, max_ubo_size;
glGetInteger64v(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
GCaps.max_uniform_buffer_size = size_t(max_ubo_size);
glGetInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size);
GCaps.max_storage_buffer_size = size_t(max_ssbo_size);
GLint ssbo_alignment;
glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &ssbo_alignment);
GCaps.storage_buffer_alignment = size_t(ssbo_alignment);
GCaps.stencil_export_support = epoxy_has_gl_extension("GL_ARB_shader_stencil_export");
/* GL specific capabilities. */
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &GCaps.max_texture_3d_size);
glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE,
reinterpret_cast<int *>(&GCaps.max_buffer_texture_size));
glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &GLContext::max_cubemap_size);
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, &GLContext::max_ubo_binds);
GLint max_ssbo_binds;
GLContext::max_ssbo_binds = 999999;
glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &max_ssbo_binds);
GLContext::max_ssbo_binds = min_ii(GLContext::max_ssbo_binds, max_ssbo_binds);
glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &max_ssbo_binds);
GLContext::max_ssbo_binds = min_ii(GLContext::max_ssbo_binds, max_ssbo_binds);
glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &max_ssbo_binds);
GLContext::max_ssbo_binds = min_ii(GLContext::max_ssbo_binds, max_ssbo_binds);
GLContext::debug_layer_support = epoxy_gl_version() >= 43 ||
epoxy_has_gl_extension("GL_KHR_debug") ||
epoxy_has_gl_extension("GL_ARB_debug_output");
GLContext::direct_state_access_support = epoxy_has_gl_extension("GL_ARB_direct_state_access");
GLContext::explicit_location_support = epoxy_gl_version() >= 43;
GLContext::framebuffer_fetch_support = epoxy_has_gl_extension("GL_EXT_shader_framebuffer_fetch");
GLContext::texture_barrier_support = epoxy_has_gl_extension("GL_ARB_texture_barrier");
GLContext::layered_rendering_support = epoxy_has_gl_extension(
"GL_ARB_shader_viewport_layer_array");
GLContext::vertex_shader_viewport_index_support = epoxy_has_gl_extension(
"GL_AMD_vertex_shader_viewport_index");
GLContext::vertex_shader_layer_support = epoxy_has_gl_extension("GL_AMD_vertex_shader_layer");
GLContext::native_barycentric_support = epoxy_has_gl_extension(
"GL_AMD_shader_explicit_vertex_parameter");
GLContext::multi_bind_support = GLContext::multi_bind_image_support = epoxy_has_gl_extension(
"GL_ARB_multi_bind");
GLContext::stencil_texturing_support = epoxy_gl_version() >= 43;
GLContext::derivative_control_support = epoxy_gl_version() >= 45 ||
epoxy_has_gl_extension("GL_ARB_derivative_control");
GLContext::texture_filter_anisotropic_support = epoxy_has_gl_extension(
"GL_EXT_texture_filter_anisotropic");
/* Disabled until it is proven to work. */
GLContext::framebuffer_fetch_support = false;
detect_workarounds();
#if BLI_SUBPROCESS_SUPPORT
GCaps.use_subprocess_shader_compilations = U.shader_compilation_method ==
USER_SHADER_COMPILE_SUBPROCESS;
#else
GCaps.use_subprocess_shader_compilations = false;
#endif
if (G.debug & G_DEBUG_GPU_RENDERDOC) {
/* Avoid crashes on RenderDoc sessions. */
GCaps.use_subprocess_shader_compilations = false;
}
int thread_count = U.gpu_shader_workers;
if (thread_count == 0) {
/* Good default based on measurements. */
/* Always have at least 1 worker. */
thread_count = 1;
if (GCaps.use_subprocess_shader_compilations) {
/* Use reasonable number of worker by default when there are known gains. */
if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) ||
GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) ||
GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY))
{
/* Subprocess is too costly in memory (>150MB per worker) to have better defaults. */
thread_count = std::max(1, std::min(4, BLI_system_thread_count() / 2));
}
}
else if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
/* Best middle ground between memory usage and speedup as Nvidia context memory footprint
* is quite heavy (~25MB). Moreover we have diminishing return after this because of PSO
* compilation blocking the main thread.
* Can be revisited if we find a way to delete the worker thread context after finishing
* compilation, and fix the scheduling bubbles (#139775). */
thread_count = 4;
}
else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OPENSOURCE) ||
GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_UNIX, GPU_DRIVER_ANY))
{
/* Mesa has very good compilation time and doesn't block the main thread.
* The memory footprint of the worker context is rather small (<10MB).
* Shader compilation gets much slower as the number of threads increases. */
thread_count = 8;
}
else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
/* AMD proprietary driver's context have huge memory footprint (~45MB).
* There is also not much gain from parallelization. */
thread_count = 1;
}
else if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY)) {
/* Intel windows driver offer almost no speedup with parallel compilation. */
thread_count = 1;
}
}
/* Allow thread count override option to limit the number of workers and avoid allocating more
* workers than needed. Also ensures that there is always 1 thread available for the UI. */
int max_thread_count = std::max(1, BLI_system_thread_count() - 1);
GCaps.max_parallel_compilations = std::min(thread_count, max_thread_count);
/* Disable this feature entirely when not debugging. */
if ((G.debug & G_DEBUG_GPU) == 0) {
GLContext::debug_layer_support = false;
GLContext::debug_layer_workaround = false;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Log extensions
* \{ */
void GLBackend::log_extensions()
{
CLOG_DEBUG(&LOG,
"OpenGL Extensions\n"
" - [%c] Multi-bind\n"
" - [%c] Direct state access\n"
" - [%c] Anisotropic Texture Filtering\n"
" - [%c] Layered rendering\n"
" - [%c] Vertex shader viewport index\n"
" - [%c] Vertex shader layer array\n"
" - [%c] Native barycentric coordinates\n"
" - [%c] Framebuffer fetch\n"
" - [%c] Texture barrier\n"
" - [%c] Shader stencil export\n"
" - [%c] Derivative control\n",
GLContext::multi_bind_support ? 'X' : ' ',
GLContext::direct_state_access_support ? 'X' : ' ',
GLContext::texture_filter_anisotropic_support ? 'X' : ' ',
GLContext::layered_rendering_support ? 'X' : ' ',
GLContext::vertex_shader_viewport_index_support ? 'X' : ' ',
GLContext::vertex_shader_layer_support ? 'X' : ' ',
GLContext::native_barycentric_support ? 'X' : ' ',
GLContext::framebuffer_fetch_support ? 'X' : ' ',
GLContext::texture_barrier_support ? 'X' : ' ',
GCaps.stencil_export_support ? 'X' : ' ',
GLContext::derivative_control_support ? 'X' : ' ');
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Log workarounds
* \{ */
void GLBackend::log_workarounds()
{
CLOG_DEBUG(&LOG,
"OpenGL Workarounds\n"
" - [%c] Debug layer workaround\n"
" - [%c] Generate mipmap workaround\n"
" - [%c] Unused framebuffer slot workaround\n"
" - [%c] Depth blitting workaround\n"
" - [%c] Stencil classify buffer workaround\n"
" - [%c] High-quality normals\n"
" - [%c] Use main context\n",
GLContext::debug_layer_workaround ? 'X' : ' ',
GLContext::generate_mipmap_workaround ? 'X' : ' ',
GLContext::unused_fb_slot_workaround ? 'X' : ' ',
GCaps.depth_blitting_workaround ? 'X' : ' ',
GCaps.stencil_clasify_buffer_workaround ? 'X' : ' ',
GCaps.use_hq_normals_workaround ? 'X' : ' ',
GCaps.use_main_context_workaround ? 'X' : ' ');
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,218 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "GPU_capabilities.hh"
#include "GPU_platform.hh"
#include "gpu_backend.hh"
#include "BLI_threads.h"
#include "BLI_vector.hh"
#include "gpu_capabilities_private.hh"
#ifdef WITH_RENDERDOC
# include "renderdoc_api.hh"
#endif
#include "gl_batch.hh"
#include "gl_compilation_subprocess.hh"
#include "gl_compute.hh"
#include "gl_context.hh"
#include "gl_framebuffer.hh"
#include "gl_index_buffer.hh"
#include "gl_query.hh"
#include "gl_shader.hh"
#include "gl_storage_buffer.hh"
#include "gl_texture.hh"
#include "gl_texture_pool.hh"
#include "gl_uniform_buffer.hh"
#include "gl_vertex_buffer.hh"
namespace blender::gpu {
class GLBackend : public GPUBackend {
private:
GLSharedOrphanLists shared_orphan_list_;
#ifdef WITH_RENDERDOC
renderdoc::api::Renderdoc renderdoc_;
#endif
Set<int> valid_contexts_;
std::mutex valid_contexts_mutex_;
public:
GLBackend()
{
/* platform_init needs to go first. */
GLBackend::platform_init();
GLBackend::capabilities_init();
GLBackend::log_extensions();
GLBackend::log_workarounds();
GLTexture::samplers_init();
}
~GLBackend()
{
GLBackend::platform_exit();
}
void init_resources() override
{
if (GCaps.use_subprocess_shader_compilations) {
compiler_ = MEM_new<GLSubprocessShaderCompiler>(__func__);
}
else {
compiler_ = MEM_new<GLShaderCompiler>(__func__);
}
};
void delete_resources() override
{
/* Delete any resources with context active. */
GLTexture::samplers_free();
MEM_delete(compiler_);
}
static GLBackend *get()
{
return static_cast<GLBackend *>(GPUBackend::get());
}
Context *context_alloc(GHOST_IWindow *ghost_window, GHOST_IContext * /*ghost_context*/) override
{
return new GLContext(ghost_window, shared_orphan_list_);
};
void add_context_id(int context_id)
{
std::lock_guard lock(valid_contexts_mutex_);
valid_contexts_.add(context_id);
}
void remove_context_id(int context_id)
{
std::lock_guard lock(valid_contexts_mutex_);
valid_contexts_.remove(context_id);
}
bool is_valid_context_id(int context_id)
{
std::lock_guard lock(valid_contexts_mutex_);
return valid_contexts_.contains(context_id);
}
Batch *batch_alloc() override
{
return new GLBatch();
};
Fence *fence_alloc() override
{
return new GLFence();
};
FrameBuffer *framebuffer_alloc(const char *name) override
{
return new GLFrameBuffer(name);
};
IndexBuf *indexbuf_alloc() override
{
return new GLIndexBuf();
};
PixelBuffer *pixelbuf_alloc(size_t size) override
{
return new GLPixelBuffer(size);
};
QueryPool *querypool_alloc() override
{
return new GLQueryPool();
};
Shader *shader_alloc(const char *name) override
{
return new GLShader(name);
};
Texture *texture_alloc(const char *name) override
{
return new GLTexture(name);
};
TexturePool *texturepool_alloc() override;
UniformBuf *uniformbuf_alloc(size_t size, const char *name) override
{
return new GLUniformBuf(size, name);
};
StorageBuf *storagebuf_alloc(size_t size, GPUUsageType usage, const char *name) override
{
return new GLStorageBuf(size, usage, name);
};
VertBuf *vertbuf_alloc() override
{
return new GLVertBuf();
};
GLSharedOrphanLists &shared_orphan_list_get()
{
return shared_orphan_list_;
};
void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) override
{
GLContext::get()->state_manager_active_get()->apply_state();
GLCompute::dispatch(groups_x_len, groups_y_len, groups_z_len);
}
void compute_dispatch_indirect(StorageBuf *indirect_buf) override
{
GLContext::get()->state_manager_active_get()->apply_state();
dynamic_cast<GLStorageBuf *>(indirect_buf)->bind_as(GL_DISPATCH_INDIRECT_BUFFER);
/* This barrier needs to be here as it only work on the currently bound indirect buffer. */
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
glDispatchComputeIndirect(GLintptr(0));
/* Unbind. */
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, 0);
}
void shader_cache_dir_clear_old() override
{
#if BLI_SUBPROCESS_SUPPORT
GL_shader_cache_dir_clear_old();
#endif
}
/* Render Frame Coordination */
void render_begin() override {};
void render_end() override {};
void render_step(bool /*force_resource_release*/) override {};
bool debug_capture_begin(const char *title);
void debug_capture_end();
private:
static void platform_init();
static void platform_exit();
static void capabilities_init();
static void log_extensions();
static void log_workarounds();
};
} // namespace blender::gpu

View File

@@ -0,0 +1,321 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GL implementation of #gpu::Batch.
* The only specificity of GL here is that it caches a list of
* Vertex Array Objects based on the bound shader interface.
*/
#include "BLI_assert.h"
#include "GPU_batch.hh"
#include "gpu_shader_private.hh"
#include "gl_context.hh"
#include "gl_debug.hh"
#include "gl_index_buffer.hh"
#include "gl_primitive.hh"
#include "gl_storage_buffer.hh"
#include "gl_vertex_array.hh"
#include "gl_batch.hh"
namespace blender {
using namespace blender::gpu;
/* -------------------------------------------------------------------- */
/** \name VAO Cache
*
* Each #GLBatch has a small cache of VAO objects that are used to avoid VAO reconfiguration.
* TODO(fclem): Could be revisited to avoid so much cross references.
* \{ */
GLVaoCache::GLVaoCache()
{
init();
}
GLVaoCache::~GLVaoCache()
{
this->clear();
}
void GLVaoCache::init()
{
context_ = nullptr;
interface_ = nullptr;
is_dynamic_vao_count = false;
for (int i = 0; i < GPU_VAO_STATIC_LEN; i++) {
static_vaos.interfaces[i] = nullptr;
static_vaos.vao_ids[i] = 0;
}
vao_base_instance_ = 0;
base_instance_ = 0;
vao_id_ = 0;
}
void GLVaoCache::insert(const GLShaderInterface *interface, GLuint vao)
{
/* Now insert the cache. */
if (!is_dynamic_vao_count) {
int i; /* find first unused slot */
for (i = 0; i < GPU_VAO_STATIC_LEN; i++) {
if (static_vaos.vao_ids[i] == 0) {
break;
}
}
if (i < GPU_VAO_STATIC_LEN) {
static_vaos.interfaces[i] = interface;
static_vaos.vao_ids[i] = vao;
}
else {
/* Erase previous entries, they will be added back if drawn again. */
for (int i = 0; i < GPU_VAO_STATIC_LEN; i++) {
if (static_vaos.interfaces[i] != nullptr) {
const_cast<GLShaderInterface *>(static_vaos.interfaces[i])->ref_remove(this);
context_->vao_free(static_vaos.vao_ids[i]);
}
}
/* Not enough place switch to dynamic. */
is_dynamic_vao_count = true;
/* Init dynamic arrays and let the branch below set the values. */
dynamic_vaos.count = GPU_BATCH_VAO_DYN_ALLOC_COUNT;
dynamic_vaos.interfaces = MEM_new_array_zeroed<const GLShaderInterface *>(
dynamic_vaos.count, "dyn vaos interfaces");
dynamic_vaos.vao_ids = MEM_new_array_zeroed<GLuint>(dynamic_vaos.count, "dyn vaos ids");
}
}
if (is_dynamic_vao_count) {
int i; /* find first unused slot */
for (i = 0; i < dynamic_vaos.count; i++) {
if (dynamic_vaos.vao_ids[i] == 0) {
break;
}
}
if (i == dynamic_vaos.count) {
/* Not enough place, realloc the array. */
i = dynamic_vaos.count;
dynamic_vaos.count += GPU_BATCH_VAO_DYN_ALLOC_COUNT;
dynamic_vaos.interfaces = static_cast<const GLShaderInterface **>(MEM_realloc_zeroed(
(void *)dynamic_vaos.interfaces, sizeof(GLShaderInterface *) * dynamic_vaos.count));
dynamic_vaos.vao_ids = static_cast<GLuint *>(
MEM_realloc_zeroed(dynamic_vaos.vao_ids, sizeof(GLuint) * dynamic_vaos.count));
}
dynamic_vaos.interfaces[i] = interface;
dynamic_vaos.vao_ids[i] = vao;
}
const_cast<GLShaderInterface *>(interface)->ref_add(this);
}
void GLVaoCache::remove(const GLShaderInterface *interface)
{
const int count = (is_dynamic_vao_count) ? dynamic_vaos.count : GPU_VAO_STATIC_LEN;
GLuint *vaos = (is_dynamic_vao_count) ? dynamic_vaos.vao_ids : static_vaos.vao_ids;
const GLShaderInterface **interfaces = (is_dynamic_vao_count) ? dynamic_vaos.interfaces :
static_vaos.interfaces;
for (int i = 0; i < count; i++) {
if (interfaces[i] == interface) {
context_->vao_free(vaos[i]);
vaos[i] = 0;
interfaces[i] = nullptr;
break; /* cannot have duplicates */
}
}
if (interface_ == interface) {
interface_ = nullptr;
vao_id_ = 0;
}
}
void GLVaoCache::clear()
{
GLContext *ctx = GLContext::get();
const int count = (is_dynamic_vao_count) ? dynamic_vaos.count : GPU_VAO_STATIC_LEN;
GLuint *vaos = (is_dynamic_vao_count) ? dynamic_vaos.vao_ids : static_vaos.vao_ids;
const GLShaderInterface **interfaces = (is_dynamic_vao_count) ? dynamic_vaos.interfaces :
static_vaos.interfaces;
/* Early out, nothing to free. */
if (context_ == nullptr) {
return;
}
if (context_ == ctx) {
glDeleteVertexArrays(count, vaos);
glDeleteVertexArrays(1, &vao_base_instance_);
}
else {
/* TODO(fclem): Slow way. Could avoid multiple mutex lock here */
for (int i = 0; i < count; i++) {
context_->vao_free(vaos[i]);
}
context_->vao_free(vao_base_instance_);
}
for (int i = 0; i < count; i++) {
if (interfaces[i] != nullptr) {
const_cast<GLShaderInterface *>(interfaces[i])->ref_remove(this);
}
}
if (is_dynamic_vao_count) {
MEM_delete(dynamic_vaos.interfaces);
MEM_delete(dynamic_vaos.vao_ids);
}
if (context_) {
context_->vao_cache_unregister(this);
}
/* Reinitialize. */
this->init();
}
GLuint GLVaoCache::lookup(const GLShaderInterface *interface)
{
const int count = (is_dynamic_vao_count) ? dynamic_vaos.count : GPU_VAO_STATIC_LEN;
const GLShaderInterface **interfaces = (is_dynamic_vao_count) ? dynamic_vaos.interfaces :
static_vaos.interfaces;
for (int i = 0; i < count; i++) {
if (interfaces[i] == interface) {
return (is_dynamic_vao_count) ? dynamic_vaos.vao_ids[i] : static_vaos.vao_ids[i];
}
}
return 0;
}
void GLVaoCache::context_check()
{
GLContext *ctx = GLContext::get();
BLI_assert(ctx);
if (context_ != ctx) {
if (context_ != nullptr) {
/* IMPORTANT: Trying to draw a batch in multiple different context will trash the VAO cache.
* This has major performance impact and should be avoided in most cases. */
context_->vao_cache_unregister(this);
}
this->clear();
context_ = ctx;
context_->vao_cache_register(this);
}
}
GLuint GLVaoCache::vao_get(Batch *batch)
{
this->context_check();
Shader *shader = GLContext::get()->shader;
GLShaderInterface *interface = static_cast<GLShaderInterface *>(shader->interface);
if (interface_ != interface) {
interface_ = interface;
vao_id_ = this->lookup(interface_);
if (vao_id_ == 0) {
/* Cache miss, create a new VAO. */
glGenVertexArrays(1, &vao_id_);
this->insert(interface_, vao_id_);
GLVertArray::update_bindings(vao_id_, batch, interface_);
}
}
return vao_id_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Drawing
* \{ */
void GLBatch::bind()
{
GLContext::get()->state_manager->apply_state();
if (flag & GPU_BATCH_DIRTY) {
flag &= ~GPU_BATCH_DIRTY;
vao_cache_.clear();
}
glBindVertexArray(vao_cache_.vao_get(this));
}
void GLBatch::draw(int v_first, int v_count, int i_first, int i_count)
{
GL_CHECK_RESOURCES("Batch");
this->bind();
BLI_assert(v_count > 0 && i_count > 0);
GLenum gl_type = to_gl(prim_type);
if (elem) {
const GLIndexBuf *el = this->elem_();
GLenum index_type = to_gl(el->index_type_);
GLint base_index = el->index_base_;
void *v_first_ofs = el->offset_ptr(v_first);
glDrawElementsInstancedBaseVertexBaseInstance(
gl_type, v_count, index_type, v_first_ofs, i_count, base_index, i_first);
}
else {
glDrawArraysInstancedBaseInstance(gl_type, v_first, v_count, i_count, i_first);
}
}
void GLBatch::draw_indirect(gpu::StorageBuf *indirect_buf, intptr_t offset)
{
GL_CHECK_RESOURCES("Batch");
this->bind();
dynamic_cast<GLStorageBuf *>(indirect_buf)->bind_as(GL_DRAW_INDIRECT_BUFFER);
GLenum gl_type = to_gl(prim_type);
if (elem) {
const GLIndexBuf *el = this->elem_();
GLenum index_type = to_gl(el->index_type_);
glDrawElementsIndirect(gl_type, index_type, reinterpret_cast<GLvoid *>(offset));
}
else {
glDrawArraysIndirect(gl_type, reinterpret_cast<GLvoid *>(offset));
}
/* Unbind. */
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
}
void GLBatch::multi_draw_indirect(gpu::StorageBuf *indirect_buf,
int count,
intptr_t offset,
intptr_t stride)
{
GL_CHECK_RESOURCES("Batch");
this->bind();
dynamic_cast<GLStorageBuf *>(indirect_buf)->bind_as(GL_DRAW_INDIRECT_BUFFER);
GLenum gl_type = to_gl(prim_type);
if (elem) {
const GLIndexBuf *el = this->elem_();
GLenum index_type = to_gl(el->index_type_);
glMultiDrawElementsIndirect(
gl_type, index_type, reinterpret_cast<GLvoid *>(offset), count, stride);
}
else {
glMultiDrawArraysIndirect(gl_type, reinterpret_cast<GLvoid *>(offset), count, stride);
}
/* Unbind. */
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,114 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU geometry batch
* Contains VAOs + VBOs + Shader representing a drawable entity.
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "GPU_batch.hh"
#include "gl_index_buffer.hh"
#include "gl_vertex_buffer.hh"
namespace blender::gpu {
class GLContext;
class GLShaderInterface;
#define GPU_VAO_STATIC_LEN 3
/**
* VAO management: remembers all geometry state (vertex attribute bindings & element buffer)
* for each shader interface. Start with a static number of VAO's and fallback to dynamic count
* if necessary. Once a batch goes dynamic it does not go back.
*/
class GLVaoCache {
private:
/** Context for which the vao_cache_ was generated. */
GLContext *context_ = nullptr;
/** Last interface this batch was drawn with. */
GLShaderInterface *interface_ = nullptr;
/** Cached VAO for the last interface. */
GLuint vao_id_ = 0;
/** Used when arb_base_instance is not supported. */
GLuint vao_base_instance_ = 0;
int base_instance_ = 0;
bool is_dynamic_vao_count = false;
union {
/** Static handle count */
struct {
const GLShaderInterface *interfaces[GPU_VAO_STATIC_LEN];
GLuint vao_ids[GPU_VAO_STATIC_LEN];
} static_vaos;
/** Dynamic handle count */
struct {
uint count;
const GLShaderInterface **interfaces;
GLuint *vao_ids;
} dynamic_vaos;
};
public:
GLVaoCache();
~GLVaoCache();
GLuint vao_get(Batch *batch);
/**
* Return 0 on cache miss (invalid VAO).
*/
GLuint lookup(const GLShaderInterface *interface);
/**
* Create a new VAO object and store it in the cache.
*/
void insert(const GLShaderInterface *interface, GLuint vao_id);
void remove(const GLShaderInterface *interface);
void clear();
private:
void init();
/**
* The #GLVaoCache object is only valid for one #GLContext.
* Reset the cache if trying to draw in another context;.
*/
void context_check();
};
class GLBatch : public Batch {
public:
/** All vaos corresponding to all the GPUShaderInterface this batch was drawn with. */
GLVaoCache vao_cache_;
public:
void draw(int v_first, int v_count, int i_first, int i_count) override;
void draw_indirect(StorageBuf *indirect_buf, intptr_t offset) override;
void multi_draw_indirect(StorageBuf *indirect_buf,
int count,
intptr_t offset,
intptr_t stride) override;
void bind();
/* Convenience getters. */
GLIndexBuf *elem_() const
{
return static_cast<GLIndexBuf *>(elem);
}
GLVertBuf *verts_(const int index) const
{
return static_cast<GLVertBuf *>(verts[index]);
}
MEM_CXX_CLASS_ALLOC_FUNCS("GLBatch");
};
} // namespace blender::gpu

View File

@@ -0,0 +1,335 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gl_compilation_subprocess.hh"
#if BLI_SUBPROCESS_SUPPORT
# include "BKE_appdir.hh"
# include "BLI_fileops.hh"
# include "BLI_hash.hh"
# include "BLI_path_utils.hh"
# include "BLI_string.h"
# include "BLI_threads.h"
# include "CLG_log.h"
# include "GHOST_IContext.hh"
# include "GHOST_ISystem.hh"
# include "GPU_context.hh"
# include "GPU_init_exit.hh"
# include "gpu_capabilities_private.hh"
# include <iostream>
# include <string>
# ifndef _WIN32
# include <unistd.h>
# else
# include "BLI_winstuff.h"
# endif
/* Include after `BLI_winstuff.h` to avoid APIENTRY redefinition. */
# include <epoxy/gl.h>
namespace blender {
namespace gpu {
class SubprocessShader {
GLuint comp_ = 0;
GLuint vert_ = 0;
GLuint geom_ = 0;
GLuint frag_ = 0;
GLuint program_ = 0;
bool success_ = false;
public:
SubprocessShader(const char *comp_src,
const char *vert_src,
const char *geom_src,
const char *frag_src)
{
GLint status;
program_ = glCreateProgram();
auto compile_stage = [&](const char *src, GLenum stage) -> GLuint {
if (src == nullptr) {
/* We only want status errors if compilation fails. */
status = GL_TRUE;
return 0;
}
GLuint shader = glCreateShader(stage);
glShaderSource(shader, 1, &src, nullptr);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
glAttachShader(program_, shader);
return shader;
};
comp_ = compile_stage(comp_src, GL_COMPUTE_SHADER);
if (!status) {
return;
}
vert_ = compile_stage(vert_src, GL_VERTEX_SHADER);
if (!status) {
return;
}
geom_ = compile_stage(geom_src, GL_GEOMETRY_SHADER);
if (!status) {
return;
}
frag_ = compile_stage(frag_src, GL_FRAGMENT_SHADER);
if (!status) {
return;
}
glLinkProgram(program_);
glGetProgramiv(program_, GL_LINK_STATUS, &status);
if (!status) {
return;
}
success_ = true;
}
~SubprocessShader()
{
glDeleteShader(comp_);
glDeleteShader(vert_);
glDeleteShader(geom_);
glDeleteShader(frag_);
glDeleteProgram(program_);
}
ShaderBinaryHeader *get_binary(void *memory)
{
ShaderBinaryHeader *bin = reinterpret_cast<ShaderBinaryHeader *>(memory);
bin->format = 0;
bin->size = 0;
if (success_) {
glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &bin->size);
if (bin->size > sizeof(ShaderBinaryHeader::data)) {
bin->size = 0;
return nullptr;
}
glGetProgramBinary(program_, bin->size, nullptr, &bin->format, bin->data);
}
return bin;
}
};
/* Check if the binary is valid and can be loaded by the driver. */
static bool validate_binary(void *binary)
{
ShaderBinaryHeader *bin = reinterpret_cast<ShaderBinaryHeader *>(binary);
GLuint program = glCreateProgram();
glProgramBinary(program, bin->format, bin->data, bin->size);
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
glDeleteProgram(program);
return status;
}
std::string GL_shader_cache_dir_get()
{
static char tmp_dir_buffer[1024];
BKE_appdir_folder_caches(tmp_dir_buffer, sizeof(tmp_dir_buffer));
std::string cache_dir = std::string(tmp_dir_buffer) + "gl-shader-cache" + SEP_STR;
BLI_dir_create_recursive(cache_dir.c_str());
return cache_dir;
}
} // namespace gpu
void GPU_compilation_subprocess_run(const char *subprocess_name)
{
using namespace blender::gpu;
# ifndef _WIN32
/** NOTE: Technically, the parent process could have crashed before this. */
pid_t ppid = getppid();
# endif
CLG_init();
BLI_threadapi_init();
/* Prevent the ShaderCompiler from spawning extra threads/contexts, we don't need them. */
GCaps.use_main_context_workaround = true;
std::string name = subprocess_name;
SharedMemory shared_mem(name, compilation_subprocess_shared_memory_size, false);
if (!shared_mem.get_data()) {
std::cerr << "Compilation Subprocess: Failed to open shared memory " << subprocess_name
<< "\n";
return;
}
SharedSemaphore start_semaphore(name + "_START", true);
SharedSemaphore end_semaphore(name + "_END", true);
SharedSemaphore close_semaphore(name + "_CLOSE", true);
GHOST_ISystem::createSystemBackground();
GHOST_ISystem *ghost_system = GHOST_ISystem::getSystem();
BLI_assert(ghost_system);
GPU_backend_ghost_system_set(ghost_system);
GHOST_GPUSettings gpu_settings = {0};
gpu_settings.context_type = GHOST_kDrawingContextTypeOpenGL;
GHOST_IContext *ghost_context = ghost_system->createOffscreenContext(gpu_settings);
if (ghost_context == nullptr) {
std::cerr << "Compilation Subprocess: Failed to initialize GHOST context for "
<< subprocess_name << "\n";
GHOST_ISystem::disposeSystem();
return;
}
ghost_context->activateDrawingContext();
GPUContext *gpu_context = GPU_context_create(nullptr, ghost_context);
GPU_init();
std::string cache_dir = GL_shader_cache_dir_get();
while (true) {
/* Process events to avoid crashes on Wayland.
* See https://bugreports.qt.io/browse/QTBUG-81504 */
ghost_system->processEvents(false);
# ifdef _WIN32
start_semaphore.decrement();
# else
bool lost_parent = false;
while (!lost_parent && !start_semaphore.try_decrement(1000)) {
lost_parent = getppid() != ppid;
}
if (lost_parent) {
std::cerr << "Compilation Subprocess: Lost parent process\n";
break;
}
# endif
if (close_semaphore.try_decrement()) {
break;
}
ShaderSourceHeader *source = reinterpret_cast<ShaderSourceHeader *>(shared_mem.get_data());
const char *next_src = source->sources;
const char *comp_src = nullptr;
const char *vert_src = nullptr;
const char *geom_src = nullptr;
const char *frag_src = nullptr;
DefaultHash<StringRefNull> hasher;
std::string hash_str = "_";
auto get_src = [&]() {
const char *src = next_src;
next_src += strlen(src) + sizeof('\0');
hash_str += std::to_string(hasher(src)) + "_";
return src;
};
if (source->type == ShaderSourceHeader::Type::COMPUTE) {
comp_src = get_src();
}
else {
vert_src = get_src();
if (source->type == ShaderSourceHeader::Type::GRAPHICS_WITH_GEOMETRY_STAGE) {
geom_src = get_src();
}
frag_src = get_src();
}
std::string cache_path = cache_dir + SEP_STR + hash_str;
/* TODO: This should lock the files? */
if (BLI_exists(cache_path.c_str())) {
{
/* Store the source hash in the shared memory.
* If the subprocess crashes, the main process will delete the cache file. */
std::string source_hash = "SOURCE_HASH:" + hash_str;
BLI_strncpy(reinterpret_cast<char *>(shared_mem.get_data()),
source_hash.c_str(),
source_hash.size() + 1);
}
/* Prevent old cache files from being deleted if they're still being used. */
BLI_file_touch(cache_path.c_str());
/* Read cached binary. */
fstream file(cache_path, std::ios::binary | std::ios::in | std::ios::ate);
std::streamsize size = file.tellg();
if (size <= compilation_subprocess_shared_memory_size) {
file.seekg(0, std::ios::beg);
/* Use temp memory so we don't overwrite the source hash. */
static char tmp_mem[compilation_subprocess_shared_memory_size];
file.read(tmp_mem, size);
/* Close first in case validation hangs the driver. */
file.close();
/* Ensure it's valid. */
if (!validate_binary(tmp_mem)) {
std::cout << "Compilation Subprocess: Failed to load cached shader binary " << hash_str
<< "\n";
/* TODO: No longer true. */
/* We can't compile the shader anymore since we have written over the source code,
* but we delete the cache for the next time this shader is requested. */
BLI_delete(cache_path.c_str(), false, false);
}
/* Copy the temp memory to the shared memory now that we know loading the shader doesn't
* crash the driver. */
memcpy(shared_mem.get_data(), tmp_mem, size);
end_semaphore.increment();
continue;
}
else {
/* This should never happen, since shaders larger than the pool size should be discarded
* and compiled in the main Blender process. */
std::cerr << "Compilation Subprocess: Wrong size for cached shader binary " << hash_str
<< "\n";
BLI_assert_unreachable();
}
}
SubprocessShader shader(comp_src, vert_src, geom_src, frag_src);
ShaderBinaryHeader *binary = shader.get_binary(shared_mem.get_data());
if (binary) {
fstream file(cache_path, std::ios::binary | std::ios::out);
file.write(reinterpret_cast<char *>(shared_mem.get_data()),
binary->size + offsetof(ShaderBinaryHeader, data));
}
end_semaphore.increment();
}
GPU_exit();
GPU_context_discard(gpu_context);
ghost_system->disposeContext(ghost_context);
GHOST_ISystem::disposeSystem();
}
namespace gpu {
void GL_shader_cache_dir_clear_old()
{
std::string cache_dir = GL_shader_cache_dir_get();
direntry *entries = nullptr;
uint32_t dir_len = BLI_filelist_dir_contents(cache_dir.c_str(), &entries);
for (int i : IndexRange(dir_len)) {
direntry entry = entries[i];
if (S_ISDIR(entry.s.st_mode)) {
continue;
}
const time_t ts_now = time(nullptr);
const time_t delete_threshold = 60 /*seconds*/ * 60 /*minutes*/ * 24 /*hours*/ * 30 /*days*/;
if (entry.s.st_mtime + delete_threshold < ts_now) {
BLI_delete(entry.path, false, false);
}
}
BLI_filelist_free(entries, dir_len);
}
} // namespace gpu
} // namespace blender
#endif

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_compilation_subprocess.hh"
#if BLI_SUBPROCESS_SUPPORT
# include "BLI_sys_types.h"
namespace blender::gpu {
/* The size of the memory pools shared by Blender and the compilation subprocesses. */
constexpr size_t compilation_subprocess_shared_memory_size = 1024 * 1024 * 5; /* 5 MiB */
struct ShaderSourceHeader {
enum Type { COMPUTE, GRAPHICS, GRAPHICS_WITH_GEOMETRY_STAGE };
/* The type of program being compiled. */
Type type;
/* The source code for all the shader stages (Separated by a null terminator).
* The stages follows the execution order (eg. vert > geom > frag). */
char sources[compilation_subprocess_shared_memory_size - sizeof(type)];
};
static_assert(sizeof(ShaderSourceHeader) == compilation_subprocess_shared_memory_size,
"Size must match the shared memory size");
struct ShaderBinaryHeader {
/* Size of the shader binary data. */
int32_t size;
/* Magic number that identifies the format of this shader binary (Driver-defined).
* This (and size) is set to 0 when the shader has failed to compile. */
uint32_t format;
/* The serialized shader binary data. */
uint8_t data[compilation_subprocess_shared_memory_size - sizeof(size) - sizeof(format)];
};
static_assert(sizeof(ShaderBinaryHeader) == compilation_subprocess_shared_memory_size,
"Size must match the shared memory size");
void GL_shader_cache_dir_clear_old();
std::string GL_shader_cache_dir_get();
} // namespace blender::gpu
#endif

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "gl_compute.hh"
#include "gl_debug.hh"
namespace blender::gpu {
void GLCompute::dispatch(int group_x_len, int group_y_len, int group_z_len)
{
GL_CHECK_RESOURCES("Compute");
/* Sometime we reference a dispatch size but we want to skip it by setting one dimension to 0.
* Avoid error being reported on some implementation for these case. */
if (group_x_len == 0 || group_y_len == 0 || group_z_len == 0) {
return;
}
glDispatchCompute(group_x_len, group_y_len, group_z_len);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
namespace blender::gpu {
class GLCompute {
public:
static void dispatch(int group_x_len, int group_y_len, int group_z_len);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,379 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_assert.h"
#include "BLI_utildefines.h"
#include "BKE_global.hh"
#include "GPU_framebuffer.hh"
#include "gpu_context_private.hh"
#include "gpu_immediate_private.hh"
#include "gl_debug.hh"
#include "gl_immediate.hh"
#include "gl_state.hh"
#include "gl_uniform_buffer.hh"
#include "gl_backend.hh" /* TODO: remove. */
#include "gl_context.hh"
namespace blender {
using namespace blender::gpu;
/* -------------------------------------------------------------------- */
/** \name Constructor / Destructor
* \{ */
GLContext::GLContext(GHOST_IWindow *ghost_window, GLSharedOrphanLists &shared_orphan_list)
: shared_orphan_list_(shared_orphan_list)
{
GLBackend::get()->add_context_id(context_id);
if (G.debug & G_DEBUG_GPU) {
debug::init_gl_callbacks();
}
float data[4] = {0.0f, 0.0f, 0.0f, 1.0f};
glGenBuffers(1, &default_attr_vbo_);
glBindBuffer(GL_ARRAY_BUFFER, default_attr_vbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
state_manager = new GLStateManager();
imm = new GLImmediate();
ghost_window_ = ghost_window;
if (ghost_window) {
GLuint default_fbo = ghost_window->getDefaultFramebuffer();
GHOST_Rect bounds;
ghost_window->getClientBounds(bounds);
const int w = bounds.getWidth();
const int h = bounds.getHeight();
if (default_fbo != 0) {
/* Bind default framebuffer, otherwise state might be undefined. */
glBindFramebuffer(GL_FRAMEBUFFER, default_fbo);
front_left = new GLFrameBuffer("front_left", this, GL_COLOR_ATTACHMENT0, default_fbo, w, h);
back_left = new GLFrameBuffer("back_left", this, GL_COLOR_ATTACHMENT0, default_fbo, w, h);
}
else {
front_left = new GLFrameBuffer("front_left", this, GL_FRONT_LEFT, 0, w, h);
back_left = new GLFrameBuffer("back_left", this, GL_BACK_LEFT, 0, w, h);
}
GLboolean supports_stereo_quad_buffer = GL_FALSE;
glGetBooleanv(GL_STEREO, &supports_stereo_quad_buffer);
if (supports_stereo_quad_buffer) {
front_right = new GLFrameBuffer("front_right", this, GL_FRONT_RIGHT, 0, w, h);
back_right = new GLFrameBuffer("back_right", this, GL_BACK_RIGHT, 0, w, h);
}
}
else {
/* For off-screen contexts. Default frame-buffer is null. */
back_left = new GLFrameBuffer("back_left", this, GL_NONE, 0, 0, 0);
}
active_fb = back_left;
static_cast<GLStateManager *>(state_manager)->active_fb = static_cast<GLFrameBuffer *>(
active_fb);
}
GLContext::~GLContext()
{
if (G.profile_gpu) {
/* Ensure query results are available. */
finish();
process_frame_timings();
}
free_resources();
BLI_assert(orphaned_framebuffers_.is_empty());
BLI_assert(orphaned_vertarrays_.is_empty());
/* For now don't allow GPUFrameBuffers to be reuse in another context. */
BLI_assert(framebuffers_.is_empty());
/* Delete VAO's so the batch can be reused in another context. */
for (GLVaoCache *cache : vao_caches_) {
cache->clear();
}
glDeleteBuffers(1, &default_attr_vbo_);
GLBackend::get()->remove_context_id(context_id);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Activate / Deactivate context
* \{ */
void GLContext::activate()
{
/* Make sure no other context is already bound to this thread. */
BLI_assert(is_active_ == false);
is_active_ = true;
thread_ = pthread_self();
/* Clear accumulated orphans. */
orphans_clear();
if (ghost_window_) {
/* Get the correct framebuffer size for the internal framebuffers. */
GHOST_Rect bounds = {0};
ghost_window_->getClientBounds(bounds);
const int w = bounds.getWidth();
const int h = bounds.getHeight();
if (front_left) {
front_left->size_set(w, h);
}
if (back_left) {
back_left->size_set(w, h);
}
if (front_right) {
front_right->size_set(w, h);
}
if (back_right) {
back_right->size_set(w, h);
}
}
/* Not really following the state but we should consider
* no ubo bound when activating a context. */
bound_ubo_slots = 0;
bound_ssbo_slots = 0;
immActivate();
}
void GLContext::deactivate()
{
immDeactivate();
is_active_ = false;
}
void GLContext::begin_frame()
{
/* No-op. */
}
void GLContext::end_frame()
{
process_frame_timings();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Flush, Finish & sync
* \{ */
void GLContext::flush()
{
glFlush();
}
void GLContext::finish()
{
glFinish();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Safe object deletion
*
* GPU objects can be freed when the context is not bound.
* In this case we delay the deletion until the context is bound again.
* \{ */
void GLSharedOrphanLists::OrphanList::clear(FunctionRef<void(GLuint, GLuint *)> free_fn)
{
std::scoped_lock lock(mutex_);
if (!handles_.is_empty()) {
free_fn(uint(handles_.size()), handles_.data());
handles_.clear();
}
};
void GLSharedOrphanLists::OrphanList::append(GLuint handle)
{
std::scoped_lock lock(mutex_);
handles_.append(handle);
};
void GLSharedOrphanLists::orphans_clear()
{
/* Check if any context is active on this thread! */
BLI_assert(GLContext::get());
buffers.clear(glDeleteBuffers);
textures.clear(glDeleteTextures);
shaders.clear([](GLuint size, GLuint *handles) {
for (uint i = 0; i < size; i++) {
glDeleteShader(handles[i]);
}
});
programs.clear([](GLuint size, GLuint *handles) {
for (uint i = 0; i < size; i++) {
glDeleteProgram(handles[i]);
}
});
};
void GLContext::orphans_clear()
{
/* Check if context has been activated by another thread! */
BLI_assert(this->is_active_on_thread());
lists_mutex_.lock();
if (!orphaned_vertarrays_.is_empty()) {
glDeleteVertexArrays(uint(orphaned_vertarrays_.size()), orphaned_vertarrays_.data());
orphaned_vertarrays_.clear();
}
if (!orphaned_framebuffers_.is_empty()) {
glDeleteFramebuffers(uint(orphaned_framebuffers_.size()), orphaned_framebuffers_.data());
orphaned_framebuffers_.clear();
}
lists_mutex_.unlock();
shared_orphan_list_.orphans_clear();
};
void GLContext::orphans_add(Vector<GLuint> &orphan_list, std::mutex &list_mutex, GLuint id)
{
list_mutex.lock();
orphan_list.append(id);
list_mutex.unlock();
}
void GLContext::vao_free(GLuint vao_id)
{
if (this == GLContext::get()) {
glDeleteVertexArrays(1, &vao_id);
}
else {
orphans_add(orphaned_vertarrays_, lists_mutex_, vao_id);
}
}
void GLContext::fbo_free(GLuint fbo_id)
{
if (this == GLContext::get()) {
glDeleteFramebuffers(1, &fbo_id);
}
else {
orphans_add(orphaned_framebuffers_, lists_mutex_, fbo_id);
}
}
void GLContext::buffer_free(GLuint buf_id)
{
/* Any context can free. */
if (GLContext::get()) {
glDeleteBuffers(1, &buf_id);
}
else {
GLSharedOrphanLists &orphan_list = GLBackend::get()->shared_orphan_list_get();
orphan_list.buffers.append(buf_id);
}
}
void GLContext::texture_free(GLuint tex_id)
{
/* Any context can free. */
if (GLContext::get()) {
glDeleteTextures(1, &tex_id);
}
else {
GLSharedOrphanLists &orphan_list = GLBackend::get()->shared_orphan_list_get();
orphan_list.textures.append(tex_id);
}
}
void GLContext::shader_free(GLuint shader_id)
{
/* Any context can free. */
if (GLContext::get()) {
glDeleteShader(shader_id);
}
else {
GLSharedOrphanLists &orphan_list = GLBackend::get()->shared_orphan_list_get();
orphan_list.shaders.append(shader_id);
}
}
void GLContext::program_free(GLuint program_id)
{
/* Any context can free. */
if (GLContext::get()) {
glDeleteProgram(program_id);
}
else {
GLSharedOrphanLists &orphan_list = GLBackend::get()->shared_orphan_list_get();
orphan_list.programs.append(program_id);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Linked object deletion
*
* These objects contain data that are stored per context. We
* need to do some cleanup if they are used across context or if context
* is discarded.
* \{ */
void GLContext::vao_cache_register(GLVaoCache *cache)
{
lists_mutex_.lock();
vao_caches_.add(cache);
lists_mutex_.unlock();
}
void GLContext::vao_cache_unregister(GLVaoCache *cache)
{
lists_mutex_.lock();
vao_caches_.remove(cache);
lists_mutex_.unlock();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Memory statistics
* \{ */
void GLContext::memory_statistics_get(int *r_total_mem, int *r_free_mem)
{
if (epoxy_has_gl_extension("GL_NVX_gpu_memory_info")) {
/* Returned value in Kb. */
glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, r_total_mem);
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, r_free_mem);
}
else if (epoxy_has_gl_extension("GL_ATI_meminfo")) {
int stats[4];
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, stats);
*r_total_mem = 0;
*r_free_mem = stats[0]; /* Total memory free in the pool. */
}
else {
*r_total_mem = 0;
*r_free_mem = 0;
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,182 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_context_private.hh"
#include "GPU_framebuffer.hh"
#include "BKE_global.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "gl_state.hh"
#include <mutex>
namespace blender::gpu {
class GLVaoCache;
class GLSharedOrphanLists {
class OrphanList {
/** Mutex for the below structures. */
std::mutex mutex_;
/** Buffers and textures are shared across context. Any context can free them. */
Vector<GLuint> handles_;
public:
void clear(FunctionRef<void(GLuint, GLuint *)> free_fn);
void append(GLuint handle);
};
public:
/** Shaders, Buffers and textures are shared across context. */
OrphanList textures;
OrphanList buffers;
OrphanList shaders;
OrphanList programs;
void orphans_clear();
};
class GLContext : public Context {
public:
/** Capabilities. */
static GLint max_cubemap_size;
static GLint max_ubo_binds;
static GLint max_ssbo_binds;
/** Extensions. */
static bool debug_layer_support;
static bool direct_state_access_support;
static bool explicit_location_support;
static bool framebuffer_fetch_support;
/* layered_rendering_support requires GL_ARB_shader_viewport_layer_array, which is a superset of
* GL_AMD_vertex_shader_viewport_index (vertex_shader_viewport_index_support) and
* GL_AMD_vertex_shader_layer (vertex_shader_layer_support) with additional support for
* tessellation evaluation shaders (which are not used by the GPU module). */
static bool layered_rendering_support;
static bool vertex_shader_viewport_index_support;
static bool vertex_shader_layer_support;
static bool native_barycentric_support;
static bool multi_bind_support;
static bool multi_bind_image_support;
static bool stencil_texturing_support;
static bool texture_barrier_support;
static bool texture_filter_anisotropic_support;
static bool derivative_control_support;
/** Workarounds. */
static bool debug_layer_workaround;
static bool unused_fb_slot_workaround;
static bool generate_mipmap_workaround;
/** VBO for missing vertex attribute binding. Avoid undefined behavior on some implementation. */
GLuint default_attr_vbo_;
/** Used for debugging purpose. Bit-flags of all bound slots. */
uint16_t bound_ubo_slots;
uint16_t bound_ssbo_slots;
private:
/**
* #Batch & #GPUFramebuffer have references to the context they are from, in the case the
* context is destroyed, we need to remove any reference to it.
*/
Set<GLVaoCache *> vao_caches_;
Set<gpu::FrameBuffer *> framebuffers_;
/** Mutex for the below structures. */
std::mutex lists_mutex_;
/** VertexArrays and framebuffers are not shared across context. */
Vector<GLuint> orphaned_vertarrays_;
Vector<GLuint> orphaned_framebuffers_;
/** #GLBackend owns this data. */
GLSharedOrphanLists &shared_orphan_list_;
struct TimeQuery {
std::string name;
union {
GLuint handles[2];
struct {
GLuint handle_start, handle_end;
};
};
bool finished;
int64_t cpu_start;
int64_t cpu_end;
};
struct FrameQueries {
Vector<TimeQuery> queries;
};
Vector<FrameQueries> frame_timings;
void process_frame_timings();
public:
GLContext(GHOST_IWindow *ghost_window, GLSharedOrphanLists &shared_orphan_list);
~GLContext();
static void check_error(const char *info);
void activate() override;
void deactivate() override;
void begin_frame() override;
void end_frame() override;
void flush() override;
void finish() override;
void memory_statistics_get(int *r_total_mem, int *r_free_mem) override;
static GLContext *get()
{
return static_cast<GLContext *>(Context::get());
}
static GLStateManager *state_manager_active_get()
{
GLContext *ctx = GLContext::get();
return static_cast<GLStateManager *>(ctx->state_manager);
};
/* These need to be called with the context the id was created with. */
void vao_free(GLuint vao_id);
void fbo_free(GLuint fbo_id);
/* These can be called by any threads even without OpenGL ctx. Deletion will be delayed. */
static void buffer_free(GLuint buf_id);
static void texture_free(GLuint tex_id);
static void shader_free(GLuint shader_id);
static void program_free(GLuint program_id);
void vao_cache_register(GLVaoCache *cache);
void vao_cache_unregister(GLVaoCache *cache);
void debug_group_begin(const char *name, int index) override;
void debug_group_end() override;
bool debug_capture_begin(const char *title) override;
void debug_capture_end() override;
void *debug_capture_scope_create(const char *name) override;
bool debug_capture_scope_begin(void *scope) override;
void debug_capture_scope_end(void *scope) override;
void debug_unbind_all_ubo() override;
void debug_unbind_all_ssbo() override;
private:
static void orphans_add(Vector<GLuint> &orphan_list, std::mutex &list_mutex, GLuint id);
void orphans_clear();
MEM_CXX_CLASS_ALLOC_FUNCS("GLContext")
};
} // namespace blender::gpu

View File

@@ -0,0 +1,558 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Debug features of OpenGL.
*/
#include "BLI_compiler_attrs.h"
#include "BLI_string.h"
#include "BLI_system.h"
#include "BLI_utildefines.h"
#include "BKE_global.hh"
#include "GPU_debug.hh"
#include "GPU_platform.hh"
#include "gpu_profile_report.hh"
#include "CLG_log.h"
#include "gl_backend.hh"
#include "gl_context.hh"
#include "gl_uniform_buffer.hh"
#include "gl_debug.hh"
namespace blender {
static CLG_LogRef LOG = {"gpu.debug"};
/* Avoid too much NVidia buffer info in the output log. */
#define TRIM_NVIDIA_BUFFER_INFO 1
/* Avoid unneeded shader statistics. */
#define TRIM_SHADER_STATS_INFO 1
namespace gpu::debug {
/* -------------------------------------------------------------------- */
/** \name Debug Callbacks
*
* Hooks up debug callbacks to a debug OpenGL context using extensions or 4.3 core debug
* capabilities.
* \{ */
/* Debug callbacks need the same calling convention as OpenGL functions. */
#if defined(_WIN32)
# define APIENTRY __stdcall
#else
# define APIENTRY
#endif
static void APIENTRY debug_callback(GLenum /*source*/,
GLenum type,
GLuint /*id*/,
GLenum severity,
GLsizei /*length*/,
const GLchar *message,
const GLvoid * /*userParm*/)
{
if (ELEM(type, GL_DEBUG_TYPE_PUSH_GROUP, GL_DEBUG_TYPE_POP_GROUP)) {
/* The debug layer will emit a message each time a debug group is pushed or popped.
* We use that for easy command grouping inside frame analyzer tools. */
return;
}
/* NOTE: callback function can be triggered during before the platform is initialized.
* In this case invoking `GPU_type_matches` would fail and
* therefore the message is checked before the platform matching. */
if (TRIM_NVIDIA_BUFFER_INFO && STRPREFIX(message, "Buffer detailed info") &&
GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL))
{
/* Suppress buffer information flooding the output. */
return;
}
if (TRIM_SHADER_STATS_INFO && STRPREFIX(message, "Shader Stats")) {
/* Suppress buffer information flooding the output. */
return;
}
const bool use_color = CLG_color_support_get(&LOG);
if (ELEM(severity, GL_DEBUG_SEVERITY_LOW, GL_DEBUG_SEVERITY_NOTIFICATION)) {
if (CLOG_CHECK(&LOG, CLG_LEVEL_INFO)) {
const char *format = use_color ? "\033[2m%s\033[0m" : "%s";
CLG_logf(LOG.type, CLG_LEVEL_INFO, "Notification", "", format, message);
}
}
else {
char debug_groups[512] = "";
GPU_debug_get_groups_names(sizeof(debug_groups), debug_groups);
CLG_Level clog_level;
if (GPU_debug_group_match(GPU_DEBUG_SHADER_COMPILATION_GROUP) ||
GPU_debug_group_match(GPU_DEBUG_SHADER_SPECIALIZATION_GROUP))
{
/* Do not duplicate shader compilation error/warnings. */
return;
}
switch (type) {
case GL_DEBUG_TYPE_ERROR:
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
clog_level = CLG_LEVEL_ERROR;
break;
case GL_DEBUG_TYPE_PORTABILITY:
case GL_DEBUG_TYPE_PERFORMANCE:
case GL_DEBUG_TYPE_OTHER:
case GL_DEBUG_TYPE_MARKER: /* KHR has this, ARB does not */
default:
clog_level = CLG_LEVEL_WARN;
break;
}
if (CLOG_CHECK(&LOG, clog_level)) {
CLG_logf(LOG.type, clog_level, debug_groups, "", "%s", message);
if (severity == GL_DEBUG_SEVERITY_HIGH) {
/* Focus on error message. */
if (use_color) {
fprintf(stderr, "\033[2m");
}
BLI_system_backtrace(stderr);
if (use_color) {
fprintf(stderr, "\033[0m\n");
}
fflush(stderr);
}
}
}
}
#undef APIENTRY
void init_gl_callbacks()
{
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(static_cast<GLDEBUGPROC>(debug_callback), nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
GL_DEBUG_TYPE_MARKER,
0,
GL_DEBUG_SEVERITY_NOTIFICATION,
-1,
"Successfully hooked OpenGL debug callback");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Error Checking
*
* This is only useful for implementation that does not support the KHR_debug extension OR when the
* implementations do not report any errors even when clearly doing shady things.
* \{ */
void check_gl_error(const char *info)
{
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
GLenum error = glGetError();
#define ERROR_CASE(err) \
case err: { \
char msg[256]; \
SNPRINTF(msg, "%s : %s", #err, info); \
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr); \
break; \
}
switch (error) {
ERROR_CASE(GL_INVALID_ENUM)
ERROR_CASE(GL_INVALID_VALUE)
ERROR_CASE(GL_INVALID_OPERATION)
ERROR_CASE(GL_INVALID_FRAMEBUFFER_OPERATION)
ERROR_CASE(GL_OUT_OF_MEMORY)
ERROR_CASE(GL_STACK_UNDERFLOW)
ERROR_CASE(GL_STACK_OVERFLOW)
case GL_NO_ERROR:
break;
default:
char msg[256];
SNPRINTF(msg, "Unknown GL error: %x : %s", error, info);
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
break;
}
}
void check_gl_resources(const char *info)
{
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
GLContext *ctx = GLContext::get();
ShaderInterface *interface = ctx->shader->interface;
/* NOTE: This only check binding. To be valid, the bound ubo needs to
* be big enough to feed the data range the shader awaits. */
uint16_t ubo_needed = interface->enabled_ubo_mask_;
ubo_needed &= ~ctx->bound_ubo_slots;
/* NOTE: This only check binding. To be valid, the bound ssbo needs to
* be big enough to feed the data range the shader awaits. */
uint16_t ssbo_needed = interface->enabled_ssbo_mask_;
ssbo_needed &= ~ctx->bound_ssbo_slots;
/* NOTE: This only check binding. To be valid, the bound texture needs to
* be the same format/target the shader expects. */
uint64_t tex_needed = interface->enabled_tex_mask_;
tex_needed &= ~GLContext::state_manager_active_get()->bound_texture_slots();
/* NOTE: This only check binding. To be valid, the bound image needs to
* be the same format/target the shader expects. */
uint8_t ima_needed = interface->enabled_ima_mask_;
ima_needed &= ~GLContext::state_manager_active_get()->bound_image_slots();
if (ubo_needed == 0 && tex_needed == 0 && ima_needed == 0 && ssbo_needed == 0) {
return;
}
for (int i = 0; ubo_needed != 0; i++, ubo_needed >>= 1) {
if ((ubo_needed & 1) != 0) {
const ShaderInput *ubo_input = interface->ubo_get(i);
const char *ubo_name = interface->input_name_get(ubo_input);
const StringRefNull sh_name = ctx->shader->name_get();
char msg[256];
SNPRINTF(
msg, "Missing UBO bind at slot %d : %s > %s : %s", i, sh_name.c_str(), ubo_name, info);
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
}
}
for (int i = 0; ssbo_needed != 0; i++, ssbo_needed >>= 1) {
if ((ssbo_needed & 1) != 0) {
const ShaderInput *ssbo_input = interface->ssbo_get(i);
const char *ssbo_name = interface->input_name_get(ssbo_input);
const StringRefNull sh_name = ctx->shader->name_get();
char msg[256];
SNPRINTF(
msg, "Missing SSBO bind at slot %d : %s > %s : %s", i, sh_name.c_str(), ssbo_name, info);
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
}
}
for (int i = 0; tex_needed != 0; i++, tex_needed >>= 1) {
if ((tex_needed & 1) != 0) {
/* FIXME: texture_get might return an image input instead. */
const ShaderInput *tex_input = interface->texture_get(i);
const char *tex_name = interface->input_name_get(tex_input);
const StringRefNull sh_name = ctx->shader->name_get();
char msg[256];
SNPRINTF(msg,
"Missing Texture bind at slot %d : %s > %s : %s",
i,
sh_name.c_str(),
tex_name,
info);
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
}
}
for (int i = 0; ima_needed != 0; i++, ima_needed >>= 1) {
if ((ima_needed & 1) != 0) {
/* FIXME: texture_get might return a texture input instead. */
const ShaderInput *tex_input = interface->texture_get(i);
const char *tex_name = interface->input_name_get(tex_input);
const StringRefNull sh_name = ctx->shader->name_get();
char msg[256];
SNPRINTF(
msg, "Missing Image bind at slot %d : %s > %s : %s", i, sh_name.c_str(), tex_name, info);
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
}
}
}
void raise_gl_error(const char *info)
{
debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, info, nullptr);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Object Label
*
* Useful for debugging through render-doc. Only defined if using `--debug-gpu`.
* Make sure to bind the object first so that it gets defined by the GL implementation.
* \{ */
static const char *to_str_prefix(GLenum type)
{
switch (type) {
case GL_FRAGMENT_SHADER:
case GL_GEOMETRY_SHADER:
case GL_VERTEX_SHADER:
case GL_SHADER:
case GL_PROGRAM:
return "SHD-";
case GL_SAMPLER:
return "SAM-";
case GL_TEXTURE:
return "TEX-";
case GL_FRAMEBUFFER:
return "FBO-";
case GL_VERTEX_ARRAY:
return "VAO-";
case GL_UNIFORM_BUFFER:
return "UBO-";
case GL_BUFFER:
return "BUF-";
default:
return "";
}
}
static const char *to_str_suffix(GLenum type)
{
switch (type) {
case GL_FRAGMENT_SHADER:
return "-Frag";
case GL_GEOMETRY_SHADER:
return "-Geom";
case GL_VERTEX_SHADER:
return "-Vert";
default:
return "";
}
}
void object_label(GLenum type, GLuint object, const char *name)
{
if ((G.debug & G_DEBUG_GPU) &&
(epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug")))
{
char label[64];
SNPRINTF(label, "%s%s%s", to_str_prefix(type), name, to_str_suffix(type));
/* Small convenience for caller. */
switch (type) {
case GL_FRAGMENT_SHADER:
case GL_GEOMETRY_SHADER:
case GL_VERTEX_SHADER:
case GL_COMPUTE_SHADER:
type = GL_SHADER;
break;
case GL_UNIFORM_BUFFER:
case GL_SHADER_STORAGE_BUFFER:
case GL_ARRAY_BUFFER:
case GL_ELEMENT_ARRAY_BUFFER:
type = GL_BUFFER;
break;
default:
break;
}
glObjectLabel(type, object, -1, label);
}
}
/** \} */
} // namespace gpu::debug
namespace gpu {
/* -------------------------------------------------------------------- */
/** \name Debug Groups
*
* Useful for debugging through render-doc. This makes all the API calls grouped into "passes".
* \{ */
void GLContext::debug_group_begin(const char *name, int index)
{
if ((G.debug & G_DEBUG_GPU) &&
(epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug")))
{
/* Add 10 to avoid collision with other indices from other possible callback layers. */
index += 10;
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, index, -1, name);
}
if (!G.profile_gpu) {
return;
}
TimeQuery query = {};
query.name = name;
query.finished = false;
glGetInteger64v(GL_TIMESTAMP, &query.cpu_start);
/* Use GL_TIMESTAMP instead of GL_ELAPSED_TIME to support nested debug groups */
glGenQueries(2, query.handles);
glQueryCounter(query.handle_start, GL_TIMESTAMP);
if (frame_timings.is_empty()) {
frame_timings.append({});
}
frame_timings.last().queries.append(query);
}
void GLContext::debug_group_end()
{
if ((G.debug & G_DEBUG_GPU) &&
(epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug")))
{
glPopDebugGroup();
}
if (!G.profile_gpu) {
return;
}
Vector<TimeQuery> &queries = frame_timings.last().queries;
for (int i = queries.size() - 1; i >= 0; i--) {
TimeQuery &query = queries[i];
if (!query.finished) {
query.finished = true;
glQueryCounter(query.handle_end, GL_TIMESTAMP);
glGetInteger64v(GL_TIMESTAMP, &query.cpu_end);
break;
}
if (i == 0) {
CLOG_ERROR(&LOG, "Profile GPU error: Extra GPU_debug_group_end() call.");
}
}
}
void GLContext::process_frame_timings()
{
if (!G.profile_gpu) {
return;
}
for (int frame_i = 0; frame_i < frame_timings.size(); frame_i++) {
Vector<TimeQuery> &queries = frame_timings[frame_i].queries;
GLint frame_is_ready = 0;
bool frame_is_valid = !queries.is_empty();
for (int i = queries.size() - 1; i >= 0; i--) {
if (!queries[i].finished) {
frame_is_valid = false;
CLOG_ERROR(&LOG, "Profile GPU error: Missing GPU_debug_group_end() call");
}
else {
glGetQueryObjectiv(queries.last().handle_end, GL_QUERY_RESULT_AVAILABLE, &frame_is_ready);
}
break;
}
if (!frame_is_valid) {
/* Cleanup. */
for (TimeQuery &query : queries) {
glDeleteQueries(2, query.handles);
}
frame_timings.remove(frame_i--);
continue;
}
if (!frame_is_ready) {
break;
}
for (TimeQuery &query : queries) {
GLuint64 gpu_start = 0;
GLuint64 gpu_end = 0;
glGetQueryObjectui64v(query.handle_start, GL_QUERY_RESULT, &gpu_start);
glGetQueryObjectui64v(query.handle_end, GL_QUERY_RESULT, &gpu_end);
glDeleteQueries(2, query.handles);
ProfileReport::get().add_group(
query.name, gpu_start, gpu_end, query.cpu_start, query.cpu_end);
}
frame_timings.remove(frame_i--);
}
frame_timings.append({});
}
bool GLContext::debug_capture_begin(const char *title)
{
return GLBackend::get()->debug_capture_begin(title);
}
bool GLBackend::debug_capture_begin(const char *title)
{
#ifdef WITH_RENDERDOC
if (G.debug & G_DEBUG_GPU_RENDERDOC) {
bool result = renderdoc_.start_frame_capture(nullptr, nullptr);
if (result && title) {
renderdoc_.set_frame_capture_title(title);
}
return result;
}
#endif
UNUSED_VARS(title);
return false;
}
void GLContext::debug_capture_end()
{
GLBackend::get()->debug_capture_end();
}
void GLBackend::debug_capture_end()
{
#ifdef WITH_RENDERDOC
if (G.debug & G_DEBUG_GPU_RENDERDOC) {
renderdoc_.end_frame_capture(nullptr, nullptr);
}
#endif
}
void *GLContext::debug_capture_scope_create(const char *name)
{
return (void *)name;
}
bool GLContext::debug_capture_scope_begin(void *scope)
{
#ifdef WITH_RENDERDOC
const char *title = (const char *)scope;
if (StringRefNull(title) != StringRefNull(G.gpu_debug_scope_name)) {
return false;
}
GLBackend::get()->debug_capture_begin(title);
#else
UNUSED_VARS(scope);
#endif
return false;
}
void GLContext::debug_capture_scope_end(void *scope)
{
#ifdef WITH_RENDERDOC
const char *title = (const char *)scope;
if (StringRefNull(title) == StringRefNull(G.gpu_debug_scope_name)) {
GLBackend::get()->debug_capture_end();
}
#else
UNUSED_VARS(scope);
#endif
}
void GLContext::debug_unbind_all_ubo()
{
this->bound_ubo_slots = 0u;
}
void GLContext::debug_unbind_all_ssbo()
{
this->bound_ssbo_slots = 0u;
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,31 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gl_context.hh"
#ifndef NDEBUG
# define GL_CHECK_RESOURCES(info) debug::check_gl_resources(info)
#else
# define GL_CHECK_RESOURCES(info)
#endif
namespace blender::gpu::debug {
void raise_gl_error(const char *info);
void check_gl_error(const char *info);
void check_gl_resources(const char *info);
/**
* This function needs to be called once per context.
*/
void init_gl_callbacks();
void object_label(GLenum type, GLuint object, const char *name);
} // namespace blender::gpu::debug

View File

@@ -0,0 +1,624 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_string.h"
#include "BKE_global.hh"
#include "gl_backend.hh"
#include "gl_debug.hh"
#include "gl_state.hh"
#include "gl_texture.hh"
#include "gl_framebuffer.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
GLFrameBuffer::GLFrameBuffer(const char *name) : FrameBuffer(name)
{
/* Just-In-Time init. See #GLFrameBuffer::init(). */
immutable_ = false;
fbo_id_ = 0;
}
GLFrameBuffer::GLFrameBuffer(
const char *name, GLContext *ctx, GLenum target, GLuint fbo, int w, int h)
: FrameBuffer(name)
{
context_ = ctx;
context_id_ = context_->context_id;
state_manager_ = static_cast<GLStateManager *>(ctx->state_manager);
immutable_ = true;
fbo_id_ = fbo;
gl_attachments_[0] = target;
set_color_attachment_bit(GPU_FB_COLOR_ATTACHMENT0, true);
/* Never update an internal frame-buffer. */
dirty_attachments_ = false;
width_ = w;
height_ = h;
srgb_ = false;
viewport_[0][0] = scissor_[0] = 0;
viewport_[0][1] = scissor_[1] = 0;
viewport_[0][2] = scissor_[2] = w;
viewport_[0][3] = scissor_[3] = h;
if (fbo_id_) {
debug::object_label(GL_FRAMEBUFFER, fbo_id_, name_);
}
}
GLFrameBuffer::~GLFrameBuffer()
{
if (context_ == nullptr) {
return;
}
if (!GLBackend::get()->is_valid_context_id(context_id_)) {
/* Context was freed. It can happen for GLTexture::framebuffer_. */
return;
}
/* Context might be partially freed. This happens when destroying the window frame-buffers. */
if (context_ == Context::get()) {
glDeleteFramebuffers(1, &fbo_id_);
}
else {
context_->fbo_free(fbo_id_);
}
/* Restore default frame-buffer if this frame-buffer was bound. */
if (context_->active_fb == this && context_->back_left != this) {
/* If this assert triggers it means the frame-buffer is being freed while in use by another
* context which, by the way, is TOTALLY UNSAFE! */
BLI_assert(context_ == Context::get());
GPU_framebuffer_restore();
}
}
void GLFrameBuffer::init()
{
context_ = GLContext::get();
context_id_ = context_->context_id;
state_manager_ = static_cast<GLStateManager *>(context_->state_manager);
glGenFramebuffers(1, &fbo_id_);
/* Binding before setting the label is needed on some drivers.
* This is not an issue since we call this function only before binding. */
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
debug::object_label(GL_FRAMEBUFFER, fbo_id_, name_);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Config
* \{ */
bool GLFrameBuffer::check(char err_out[256])
{
this->bind(true);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
#define FORMAT_STATUS(X) \
case X: { \
err = #X; \
break; \
}
const char *err;
switch (status) {
FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
FORMAT_STATUS(GL_FRAMEBUFFER_UNSUPPORTED);
FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER);
FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER);
FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS);
FORMAT_STATUS(GL_FRAMEBUFFER_UNDEFINED);
case GL_FRAMEBUFFER_COMPLETE:
return true;
default:
err = "unknown";
break;
}
#undef FORMAT_STATUS
const char *format = "gpu::FrameBuffer: %s status %s\n";
if (err_out) {
BLI_snprintf(err_out, 256, format, this->name_, err);
}
else {
fprintf(stderr, format, this->name_, err);
}
return false;
}
void GLFrameBuffer::update_attachments()
{
/* Default frame-buffers cannot have attachments. */
BLI_assert(immutable_ == false);
/* First color texture OR the depth texture if no color is attached.
* Used to determine frame-buffer color-space and dimensions. */
GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHMENT;
/* NOTE: Inverse iteration to get the first color texture. */
for (GPUAttachmentType type = GPU_FB_MAX_ATTACHMENT - 1; type >= 0; --type) {
GPUAttachment &attach = attachments_[type];
GLenum gl_attachment = to_gl(type);
if (type >= GPU_FB_COLOR_ATTACHMENT0) {
gl_attachments_[type - GPU_FB_COLOR_ATTACHMENT0] = (attach.tex) ? gl_attachment : GL_NONE;
first_attachment = (attach.tex) ? type : first_attachment;
}
else if (first_attachment == GPU_FB_MAX_ATTACHMENT) {
/* Only use depth texture to get information if there is no color attachment. */
first_attachment = (attach.tex) ? type : first_attachment;
}
if (attach.tex == nullptr) {
glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, 0, 0);
continue;
}
GLuint gl_tex = static_cast<GLTexture *>(attach.tex)->tex_id_;
if (attach.layer > -1 && GPU_texture_is_cube(attach.tex) && !GPU_texture_is_array(attach.tex))
{
/* Could be avoided if ARB_direct_state_access is required. In this case
* #glFramebufferTextureLayer would bind the correct face. */
GLenum gl_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + attach.layer;
glFramebufferTexture2D(GL_FRAMEBUFFER, gl_attachment, gl_target, gl_tex, attach.mip);
}
else if (attach.layer > -1) {
glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_attachment, gl_tex, attach.mip, attach.layer);
}
else {
/* The whole texture level is attached. The frame-buffer is potentially layered. */
glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, gl_tex, attach.mip);
}
/* We found one depth buffer type. Stop here, otherwise we would
* override it by setting GPU_FB_DEPTH_ATTACHMENT */
if (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT) {
break;
}
}
if (GLContext::unused_fb_slot_workaround) {
/* Fill normally un-occupied slots to avoid rendering artifacts on some hardware. */
GLuint gl_tex = 0;
/* NOTE: Inverse iteration to get the first color texture. */
for (int i = ARRAY_SIZE(gl_attachments_) - 1; i >= 0; --i) {
GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i;
GPUAttachment &attach = attachments_[type];
if (attach.tex != nullptr) {
gl_tex = static_cast<GLTexture *>(attach.tex)->tex_id_;
}
else if (gl_tex != 0) {
GLenum gl_attachment = to_gl(type);
gl_attachments_[i] = gl_attachment;
glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, gl_tex, 0);
}
}
}
if (first_attachment != GPU_FB_MAX_ATTACHMENT) {
GPUAttachment &attach = attachments_[first_attachment];
int size[3];
GPU_texture_get_mipmap_size(attach.tex, attach.mip, size);
this->size_set(size[0], size[1]);
srgb_ = (GPU_texture_format(attach.tex) == TextureFormat::SRGBA_8_8_8_8);
}
else {
/* Empty frame-buffer. */
glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, width_);
glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, height_);
}
dirty_attachments_ = false;
glDrawBuffers(ARRAY_SIZE(gl_attachments_), gl_attachments_);
if (G.debug & G_DEBUG_GPU) {
BLI_assert(this->check(nullptr));
}
}
void GLFrameBuffer::subpass_transition_impl(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states)
{
GPU_depth_mask(depth_attachment_state == GPU_ATTACHMENT_WRITE);
bool any_read = false;
for (auto attachment : color_attachment_states.index_range()) {
if (attachment == GPU_ATTACHMENT_READ) {
any_read = true;
break;
}
}
if (GLContext::framebuffer_fetch_support) {
if (any_read) {
glFramebufferFetchBarrierEXT();
}
}
else if (GLContext::texture_barrier_support) {
if (any_read) {
glTextureBarrier();
}
GLenum attachments[GPU_FB_MAX_COLOR_ATTACHMENT] = {GL_NONE};
for (int i : color_attachment_states.index_range()) {
GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i;
gpu::Texture *attach_tex = this->attachments_[type].tex;
if (color_attachment_states[i] == GPU_ATTACHMENT_READ) {
tmp_detached_[type] = this->attachments_[type]; /* Bypass feedback loop check. */
GPU_texture_bind_ex(attach_tex, GPUSamplerState::default_sampler(), i);
}
else {
tmp_detached_[type] = GPU_ATTACHMENT_NONE;
}
bool attach_write = color_attachment_states[i] == GPU_ATTACHMENT_WRITE;
attachments[i] = (attach_tex && attach_write) ? to_gl(type) : GL_NONE;
}
/* We have to use `glDrawBuffers` instead of `glColorMaski` because the later is overwritten
* by the `GLStateManager`. */
/* WATCH(fclem): This modifies the frame-buffer state without setting `dirty_attachments_`. */
glDrawBuffers(ARRAY_SIZE(attachments), attachments);
}
else {
/* The only way to have correct visibility without extensions and ensure defined behavior, is
* to unbind the textures and update the frame-buffer. This is a slow operation but that's all
* we can do to emulate the sub-pass input. */
/* TODO(@fclem): Could avoid the frame-buffer reconfiguration by creating multiple
* frame-buffers internally. */
for (int i : color_attachment_states.index_range()) {
GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i;
if (color_attachment_states[i] == GPU_ATTACHMENT_WRITE) {
if (tmp_detached_[type].tex != nullptr) {
/* Re-attach previous read attachments. */
this->attachment_set(type, tmp_detached_[type]);
tmp_detached_[type] = GPU_ATTACHMENT_NONE;
}
}
else if (color_attachment_states[i] == GPU_ATTACHMENT_READ) {
tmp_detached_[type] = this->attachments_[type];
tmp_detached_[type].tex->detach_from(this);
GPU_texture_bind_ex(tmp_detached_[type].tex, GPUSamplerState::default_sampler(), i);
}
}
if (dirty_attachments_) {
this->update_attachments();
}
}
}
void GLFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls)
{
BLI_assert(context_->active_fb == this);
/* TODO(fclem): Add support for other ops. */
if (ls.load_action == GPULoadOp::GPU_LOADACTION_CLEAR) {
if (tmp_detached_[type].tex != nullptr) {
/* #GPULoadStore is used to define the frame-buffer before it is used for rendering.
* Binding back unattached attachment makes its state undefined. This is described by the
* documentation and the user-land code should specify a sub-pass at the start of the drawing
* to explicitly set attachment state. */
if (GLContext::framebuffer_fetch_support) {
/* NOOP. */
}
else if (GLContext::texture_barrier_support) {
/* Reset default attachment state. */
for (int i : IndexRange(ARRAY_SIZE(tmp_detached_))) {
tmp_detached_[i] = GPU_ATTACHMENT_NONE;
}
glDrawBuffers(ARRAY_SIZE(gl_attachments_), gl_attachments_);
}
else {
tmp_detached_[type] = GPU_ATTACHMENT_NONE;
this->attachment_set(type, tmp_detached_[type]);
this->update_attachments();
}
}
clear_attachment(type, ls.clear_value);
}
}
void GLFrameBuffer::apply_state()
{
if (dirty_state_ == false) {
return;
}
if (multi_viewport_ == false) {
glViewport(UNPACK4(viewport_[0]));
}
else {
/* Great API you have there! You have to convert to float values for setting int viewport
* values. **Audible Facepalm** */
float viewports_f[GPU_MAX_VIEWPORTS][4];
for (int i = 0; i < GPU_MAX_VIEWPORTS; i++) {
for (int j = 0; j < 4; j++) {
viewports_f[i][j] = viewport_[i][j];
}
}
glViewportArrayv(0, GPU_MAX_VIEWPORTS, viewports_f[0]);
}
glScissor(UNPACK4(scissor_));
if (scissor_test_) {
glEnable(GL_SCISSOR_TEST);
}
else {
glDisable(GL_SCISSOR_TEST);
}
dirty_state_ = false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Binding
* \{ */
void GLFrameBuffer::bind(bool enabled_srgb)
{
if (!immutable_ && fbo_id_ == 0) {
this->init();
}
if (context_ != GLContext::get()) {
BLI_assert_msg(0, "Trying to use the same frame-buffer in multiple context");
return;
}
if (context_->active_fb != this) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
/* Internal frame-buffers have only one color output and needs to be set every time. */
if (immutable_ && fbo_id_ == 0) {
glDrawBuffer(gl_attachments_[0]);
}
}
if (!GLContext::texture_barrier_support && !GLContext::framebuffer_fetch_support) {
for (int index : IndexRange(GPU_FB_MAX_ATTACHMENT)) {
tmp_detached_[index] = GPU_ATTACHMENT_NONE;
}
}
if (dirty_attachments_) {
this->update_attachments();
this->viewport_reset();
this->scissor_reset();
}
if (context_->active_fb != this || enabled_srgb_ != enabled_srgb) {
enabled_srgb_ = enabled_srgb;
if (enabled_srgb && srgb_) {
glEnable(GL_FRAMEBUFFER_SRGB);
}
else {
glDisable(GL_FRAMEBUFFER_SRGB);
}
Shader::set_framebuffer_srgb_target(enabled_srgb && srgb_);
}
if (context_->active_fb != this) {
context_->active_fb = this;
state_manager_->active_fb = this;
dirty_state_ = true;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operations.
* \{ */
void GLFrameBuffer::clear(GPUFrameBufferBits buffers,
const double4 clear_col,
float clear_depth,
uint clear_stencil)
{
BLI_assert(GLContext::get() == context_);
BLI_assert(context_->active_fb == this);
/* Save and restore the state. */
GPUWriteMask write_mask = GPU_write_mask_get();
uint stencil_mask = GPU_stencil_mask_get();
GPUStencilTest stencil_test = GPU_stencil_test_get();
if (buffers & GPU_COLOR_BIT) {
if (immutable_) {
/* Immutable frame-buffers (default/window) have no texture attachments,
* clear via #glClearColor + #glClear (included in the mask below). */
GPU_color_mask(true, true, true, true);
glClearColor(
float(clear_col[0]), float(clear_col[1]), float(clear_col[2]), float(clear_col[3]));
}
else {
int type = GPU_FB_COLOR_ATTACHMENT0;
for (int i = 0; type < GPU_FB_MAX_ATTACHMENT; i++, type++) {
if (attachments_[type].tex != nullptr) {
this->clear_attachment(GPU_FB_COLOR_ATTACHMENT0 + i, clear_col);
}
}
}
}
if (buffers & GPU_DEPTH_BIT) {
GPU_depth_mask(true);
glClearDepth(clear_depth);
}
if (buffers & GPU_STENCIL_BIT) {
GPU_stencil_write_mask_set(0xFFu);
GPU_stencil_test(GPU_STENCIL_ALWAYS);
glClearStencil(clear_stencil);
}
context_->state_manager->apply_state();
/* Mutable frame-buffers clear color via per-attachment calls above,
* exclude color from the #glClear mask in that case. */
GPUFrameBufferBits glclear_buffers = immutable_ ? buffers :
GPUFrameBufferBits(buffers & ~GPU_COLOR_BIT);
GLbitfield mask = to_gl(glclear_buffers);
glClear(mask);
if (buffers & (GPU_COLOR_BIT | GPU_DEPTH_BIT)) {
GPU_write_mask(write_mask);
}
if (buffers & GPU_STENCIL_BIT) {
GPU_stencil_write_mask_set(stencil_mask);
GPU_stencil_test(stencil_test);
}
}
void GLFrameBuffer::clear_attachment(GPUAttachmentType type, const double4 clear_value)
{
BLI_assert(GLContext::get() == context_);
BLI_assert(context_->active_fb == this);
/* Save and restore the state. */
GPUWriteMask write_mask = GPU_write_mask_get();
GPU_depth_mask(true);
GPU_color_mask(true, true, true, true);
context_->state_manager->apply_state();
if (ELEM(type, GPU_FB_DEPTH_ATTACHMENT, GPU_FB_DEPTH_STENCIL_ATTACHMENT)) {
glClearDepth(float(clear_value.x));
glClear(to_gl(GPU_DEPTH_BIT));
}
else {
int slot = type - GPU_FB_COLOR_ATTACHMENT0;
GPUTextureFormatFlag flag = attachments_[type].tex->format_flag_get();
if (flag & GPU_FORMAT_FLOAT || flag & GPU_FORMAT_NORMALIZED_INTEGER) {
float4 data = float4(clear_value);
glClearBufferfv(GL_COLOR, slot, &data.x);
}
else if (flag & GPU_FORMAT_INTEGER && flag & GPU_FORMAT_SIGNED) {
int4 data = int4(clear_value);
glClearBufferiv(GL_COLOR, slot, &data.x);
}
else if (flag & GPU_FORMAT_INTEGER && !(flag & GPU_FORMAT_SIGNED)) {
uint4 data = uint4(clear_value);
glClearBufferuiv(GL_COLOR, slot, &data.x);
}
else {
BLI_assert_msg(0, "Unhandled data format");
}
}
GPU_write_mask(write_mask);
}
void GLFrameBuffer::clear_multi(Span<double4> clear_cols)
{
int type = GPU_FB_COLOR_ATTACHMENT0;
for (int i = 0; type < GPU_FB_MAX_ATTACHMENT; i++, type++) {
if (attachments_[type].tex != nullptr) {
this->clear_attachment(GPU_FB_COLOR_ATTACHMENT0 + i, clear_cols[i]);
}
}
}
void GLFrameBuffer::read(GPUFrameBufferBits plane,
eGPUDataFormat data_format,
const int area[4],
int channel_len,
int slot,
void *r_data)
{
GLenum format, type, mode;
mode = gl_attachments_[slot];
type = to_gl(data_format);
switch (plane) {
case GPU_DEPTH_BIT:
format = GL_DEPTH_COMPONENT;
BLI_assert_msg(
this->attachments_[GPU_FB_DEPTH_ATTACHMENT].tex != nullptr ||
this->attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex != nullptr,
"GPUFramebuffer: Error: Trying to read depth without a depth buffer attached.");
break;
case GPU_COLOR_BIT:
BLI_assert_msg(
mode != GL_NONE,
"GPUFramebuffer: Error: Trying to read a color slot without valid attachment.");
format = channel_len_to_gl(channel_len);
/* TODO: needed for selection buffers to work properly, this should be handled better. */
if (format == GL_RED && type == GL_UNSIGNED_INT) {
format = GL_RED_INTEGER;
}
break;
case GPU_STENCIL_BIT:
fprintf(stderr, "GPUFramebuffer: Error: Trying to read stencil bit. Unsupported.");
return;
default:
fprintf(stderr, "GPUFramebuffer: Error: Trying to read more than one frame-buffer plane.");
return;
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_id_);
glReadBuffer(mode);
glReadPixels(UNPACK4(area), format, type, r_data);
}
void GLFrameBuffer::blit_to(
GPUFrameBufferBits planes, int src_slot, FrameBuffer *dst_, int dst_slot, int x, int y)
{
GLFrameBuffer *src = this;
GLFrameBuffer *dst = static_cast<GLFrameBuffer *>(dst_);
/* Frame-buffers must be up to date. This simplify this function. */
if (src->dirty_attachments_) {
src->bind(true);
}
if (dst->dirty_attachments_) {
dst->bind(true);
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, src->fbo_id_);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->fbo_id_);
if (planes & GPU_COLOR_BIT) {
BLI_assert(src->immutable_ == false || src_slot == 0);
BLI_assert(dst->immutable_ == false || dst_slot == 0);
BLI_assert(src->gl_attachments_[src_slot] != GL_NONE);
BLI_assert(dst->gl_attachments_[dst_slot] != GL_NONE);
glReadBuffer(src->gl_attachments_[src_slot]);
glDrawBuffer(dst->gl_attachments_[dst_slot]);
}
context_->state_manager->apply_state();
int w = src->width_;
int h = src->height_;
GLbitfield mask = to_gl(planes);
glBlitFramebuffer(0, 0, w, h, x, y, x + w, y + h, mask, GL_NEAREST);
if (!dst->immutable_) {
/* Restore the draw buffers. */
glDrawBuffers(ARRAY_SIZE(dst->gl_attachments_), dst->gl_attachments_);
}
/* Ensure previous buffer is restored. */
context_->active_fb = dst;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,164 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Encapsulation of Frame-buffer states (attached textures, viewport, scissors).
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "gpu_framebuffer_private.hh"
namespace blender::gpu {
class GLStateManager;
/**
* Implementation of FrameBuffer object using OpenGL.
*/
class GLFrameBuffer : public FrameBuffer {
/* For debugging purpose. */
friend class GLTexture;
private:
/** OpenGL handle. */
GLuint fbo_id_ = 0;
/** Context the handle is from. Frame-buffers are not shared across contexts. */
GLContext *context_ = nullptr;
/** WORKAROUND: GLTexture::framebuffer_ can outlive its context.
* We track the context id to ensure we don't try to use context_ after its been freed. */
int context_id_ = -1;
/** State Manager of the same contexts. */
GLStateManager *state_manager_ = nullptr;
/** Copy of the GL state. Contains ONLY color attachments enums for slot binding. */
GLenum gl_attachments_[GPU_FB_MAX_COLOR_ATTACHMENT] = {0};
/** List of attachment that are associated with this frame-buffer but temporarily detached. */
GPUAttachment tmp_detached_[GPU_FB_MAX_ATTACHMENT];
/** Internal frame-buffers are immutable. */
bool immutable_ = false;
/** True is the frame-buffer has its first color target using the
* TextureFormat::SRGBA_8_8_8_8 format. */
bool srgb_ = false;
/** True is the frame-buffer has been bound using the GL_FRAMEBUFFER_SRGB feature. */
bool enabled_srgb_ = false;
public:
/**
* Create a conventional frame-buffer to attach texture to.
*/
GLFrameBuffer(const char *name);
/**
* Special frame-buffer encapsulating internal window frame-buffer.
* (i.e.: #GL_FRONT_LEFT, #GL_BACK_RIGHT, ...)
* \param ctx: Context the handle is from.
* \param target: The internal GL name (i.e: #GL_BACK_LEFT).
* \param fbo: The (optional) already created object for some implementation. Default is 0.
* \param w: Buffer width.
* \param h: Buffer height.
*/
GLFrameBuffer(const char *name, GLContext *ctx, GLenum target, GLuint fbo, int w, int h);
~GLFrameBuffer();
void bind(bool enabled_srgb) override;
/**
* This is a rather slow operation. Don't check in normal cases.
*/
bool check(char err_out[256]) override;
void clear(GPUFrameBufferBits buffers,
const double4 clear_col,
float clear_depth,
uint clear_stencil) override;
void clear_multi(Span<double4> clear_cols) override;
void clear_attachment(GPUAttachmentType type, const double4 clear_value) override;
/* Attachment load-stores are currently no-op's in OpenGL. */
void attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls) override;
protected:
void subpass_transition_impl(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states) override;
public:
void read(GPUFrameBufferBits planes,
eGPUDataFormat format,
const int area[4],
int channel_len,
int slot,
void *r_data) override;
/**
* Copy \a src at the give offset inside \a dst.
*/
void blit_to(GPUFrameBufferBits planes,
int src_slot,
FrameBuffer *dst,
int dst_slot,
int dst_offset_x,
int dst_offset_y) override;
GLContext *context_get() const
{
return context_;
}
void apply_state();
private:
void init();
void update_attachments();
void update_drawbuffers();
MEM_CXX_CLASS_ALLOC_FUNCS("GLFrameBuffer");
};
/* -------------------------------------------------------------------- */
/** \name Enums Conversion
* \{ */
static inline GLenum to_gl(const GPUAttachmentType type)
{
#define ATTACHMENT(X) \
case GPU_FB_##X: { \
return GL_##X; \
} \
((void)0)
switch (type) {
ATTACHMENT(DEPTH_ATTACHMENT);
ATTACHMENT(DEPTH_STENCIL_ATTACHMENT);
ATTACHMENT(COLOR_ATTACHMENT0);
ATTACHMENT(COLOR_ATTACHMENT1);
ATTACHMENT(COLOR_ATTACHMENT2);
ATTACHMENT(COLOR_ATTACHMENT3);
ATTACHMENT(COLOR_ATTACHMENT4);
ATTACHMENT(COLOR_ATTACHMENT5);
ATTACHMENT(COLOR_ATTACHMENT6);
ATTACHMENT(COLOR_ATTACHMENT7);
default:
BLI_assert(0);
return GL_COLOR_ATTACHMENT0;
}
#undef ATTACHMENT
}
static inline GLbitfield to_gl(const GPUFrameBufferBits bits)
{
GLbitfield mask = 0;
mask |= (bits & GPU_DEPTH_BIT) ? GL_DEPTH_BUFFER_BIT : 0;
mask |= (bits & GPU_STENCIL_BIT) ? GL_STENCIL_BUFFER_BIT : 0;
mask |= (bits & GPU_COLOR_BIT) ? GL_COLOR_BUFFER_BIT : 0;
return mask;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,198 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Mimics old style opengl immediate mode drawing.
*/
#include "GPU_capabilities.hh"
#include "gpu_context_private.hh"
#include "gpu_shader_private.hh"
#include "gpu_vertex_format_private.hh"
#include "gl_context.hh"
#include "gl_debug.hh"
#include "gl_primitive.hh"
#include "gl_vertex_array.hh"
#include "gl_immediate.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
GLImmediate::GLImmediate()
{
glGenVertexArrays(1, &vao_id_);
glBindVertexArray(vao_id_); /* Necessary for glObjectLabel. */
buffer.buffer_size = DEFAULT_INTERNAL_BUFFER_SIZE;
glGenBuffers(1, &buffer.vbo_id);
glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id);
glBufferData(GL_ARRAY_BUFFER, buffer.buffer_size, nullptr, GL_DYNAMIC_DRAW);
buffer_strict.buffer_size = DEFAULT_INTERNAL_BUFFER_SIZE;
glGenBuffers(1, &buffer_strict.vbo_id);
glBindBuffer(GL_ARRAY_BUFFER, buffer_strict.vbo_id);
glBufferData(GL_ARRAY_BUFFER, buffer_strict.buffer_size, nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
debug::object_label(GL_VERTEX_ARRAY, vao_id_, "Immediate");
debug::object_label(GL_BUFFER, buffer.vbo_id, "ImmediateVbo");
debug::object_label(GL_BUFFER, buffer_strict.vbo_id, "ImmediateVboStrict");
}
GLImmediate::~GLImmediate()
{
glDeleteVertexArrays(1, &vao_id_);
glDeleteBuffers(1, &buffer.vbo_id);
glDeleteBuffers(1, &buffer_strict.vbo_id);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Buffer management
* \{ */
uchar *GLImmediate::begin()
{
/* How many bytes do we need for this draw call? */
const size_t bytes_needed = vertex_buffer_size(&vertex_format, vertex_len);
/* Does the current buffer have enough room? */
const size_t available_bytes = buffer_size() - buffer_offset();
#ifndef NDEBUG
if (this->shader->is_polyline) {
/* Silence error. These are bound inside `immEnd()`. */
GLContext::get()->bound_ssbo_slots |= 1 << GPU_SSBO_POLYLINE_POS_BUF_SLOT;
GLContext::get()->bound_ssbo_slots |= 1 << GPU_SSBO_POLYLINE_COL_BUF_SLOT;
GLContext::get()->bound_ssbo_slots |= 1 << GPU_SSBO_INDEX_BUF_SLOT;
}
#endif
GL_CHECK_RESOURCES("Immediate");
glBindBuffer(GL_ARRAY_BUFFER, vbo_id());
bool recreate_buffer = false;
if (bytes_needed > buffer_size()) {
/* expand the internal buffer */
buffer_size() = bytes_needed;
recreate_buffer = true;
}
else if (bytes_needed < DEFAULT_INTERNAL_BUFFER_SIZE &&
buffer_size() > DEFAULT_INTERNAL_BUFFER_SIZE)
{
/* shrink the internal buffer */
buffer_size() = DEFAULT_INTERNAL_BUFFER_SIZE;
recreate_buffer = true;
}
uint vert_alignment = vertex_format.stride;
if (this->shader->is_polyline) {
/* Polyline needs to bind the buffer as SSBO.
* The start of the range needs to match the SSBO alignment requirements. */
vert_alignment = ceil_to_multiple_u(vert_alignment, GPU_storage_buffer_alignment());
}
/* Ensure vertex data is aligned. Might waste a little space, but it's safe. */
const uint pre_padding = padding(buffer_offset(), vert_alignment);
if (!recreate_buffer && ((bytes_needed + pre_padding) <= available_bytes)) {
buffer_offset() += pre_padding;
}
else {
/* orphan this buffer & start with a fresh one */
glBufferData(GL_ARRAY_BUFFER, buffer_size(), nullptr, GL_DYNAMIC_DRAW);
buffer_offset() = 0;
}
#ifndef NDEBUG
{
GLint bufsize;
glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &bufsize);
BLI_assert(buffer_offset() + bytes_needed <= bufsize);
}
#endif
GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
if (!strict_vertex_len) {
access |= GL_MAP_FLUSH_EXPLICIT_BIT;
}
void *data = glMapBufferRange(GL_ARRAY_BUFFER, buffer_offset(), bytes_needed, access);
BLI_assert(data != nullptr);
bytes_mapped_ = bytes_needed;
return static_cast<uchar *>(data);
}
void GLImmediate::end()
{
BLI_assert(prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
uint buffer_bytes_used = bytes_mapped_;
if (!strict_vertex_len) {
if (vertex_idx != vertex_len) {
vertex_len = vertex_idx;
buffer_bytes_used = vertex_buffer_size(&vertex_format, vertex_len);
/* unused buffer bytes are available to the next immBegin */
}
/* tell OpenGL what range was modified so it doesn't copy the whole mapped range */
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, buffer_bytes_used);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
if (vertex_len == 0) {
/* NOOP. Nothing to draw. */
}
else if (this->shader->is_polyline) {
GLintptr offset = buffer_offset();
GLenum target = GL_SHADER_STORAGE_BUFFER;
glBindBufferRange(target, GPU_SSBO_POLYLINE_POS_BUF_SLOT, vbo_id(), offset, buffer_bytes_used);
glBindBufferRange(target, GPU_SSBO_POLYLINE_COL_BUF_SLOT, vbo_id(), offset, buffer_bytes_used);
/* Not used. Satisfy the binding. */
glBindBufferRange(target, GPU_SSBO_INDEX_BUF_SLOT, vbo_id(), offset, buffer_bytes_used);
this->polyline_draw_workaround(0);
#ifndef NDEBUG
GLContext::get()->bound_ssbo_slots &= ~(1 << GPU_SSBO_POLYLINE_POS_BUF_SLOT);
GLContext::get()->bound_ssbo_slots &= ~(1 << GPU_SSBO_POLYLINE_COL_BUF_SLOT);
GLContext::get()->bound_ssbo_slots &= ~(1 << GPU_SSBO_INDEX_BUF_SLOT);
#endif
}
else {
GLContext::get()->state_manager->apply_state();
/* We convert the offset in vertex offset from the buffer's start.
* This works because we added some padding to align the first vertex. */
uint v_first = buffer_offset() / vertex_format.stride;
GLVertArray::update_bindings(
vao_id_, v_first, &vertex_format, reinterpret_cast<Shader *>(shader)->interface);
/* Update matrices. */
GPU_shader_bind(shader);
glDrawArrays(to_gl(prim_type), 0, vertex_len);
/* These lines are causing crash on startup on some old GPU + drivers.
* They are not required so just comment them. (#55722) */
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// glBindVertexArray(0);
}
buffer_offset() += buffer_bytes_used;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Mimics old style opengl immediate mode drawing.
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "gpu_immediate_private.hh"
namespace blender::gpu {
/* size of internal buffer */
#define DEFAULT_INTERNAL_BUFFER_SIZE (4 * 1024 * 1024)
class GLImmediate : public Immediate {
private:
/* Use two buffers for strict and non-strict vertex count to
* avoid some huge driver slowdown (see #70922).
* Use accessor functions to get / modify. */
struct {
/** Opengl Handle for this buffer. */
GLuint vbo_id = 0;
/** Offset of the mapped data in data. */
size_t buffer_offset = 0;
/** Size of the whole buffer in bytes. */
size_t buffer_size = 0;
} buffer, buffer_strict;
/** Size in bytes of the mapped region. */
size_t bytes_mapped_ = 0;
/** Vertex array for this immediate mode instance. */
GLuint vao_id_ = 0;
public:
GLImmediate();
~GLImmediate();
uchar *begin() override;
void end() override;
private:
GLuint &vbo_id()
{
return strict_vertex_len ? buffer_strict.vbo_id : buffer.vbo_id;
};
size_t &buffer_offset()
{
return strict_vertex_len ? buffer_strict.buffer_offset : buffer.buffer_offset;
};
size_t &buffer_size()
{
return strict_vertex_len ? buffer_strict.buffer_size : buffer.buffer_size;
};
};
} // namespace blender::gpu

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "gl_context.hh"
#include "gl_index_buffer.hh"
namespace blender::gpu {
GLIndexBuf::~GLIndexBuf()
{
GLContext::buffer_free(ibo_id_);
}
void GLIndexBuf::bind()
{
if (is_subrange_) {
static_cast<GLIndexBuf *>(src_)->bind();
return;
}
const bool allocate_on_device = ibo_id_ == 0;
if (allocate_on_device) {
glGenBuffers(1, &ibo_id_);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id_);
if (data_ != nullptr || allocate_on_device) {
size_t size = this->size_get();
/* Pad the buffer to avoid out of bound reads when using vertex pulling mode. */
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ceil_to_multiple_ul(size, 16), nullptr, GL_STATIC_DRAW);
if (data_ != nullptr) {
/* Sends data to GPU. */
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, size, data_);
}
/* No need to keep copy of data in system memory. */
MEM_SAFE_DELETE_VOID(data_);
}
}
void GLIndexBuf::bind_as_ssbo(uint binding)
{
if (is_subrange_) {
src_->bind_as_ssbo(binding);
return;
}
if (ibo_id_ == 0 || data_ != nullptr) {
/* Calling `glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id_)` changes the index buffer
* of the currently bound VAO.
*
* In the OpenGL backend, the VAO state persists even after `GLVertArray::update_bindings`
* is called.
*
* NOTE: For safety, we could call `glBindVertexArray(0)` right after drawing a `gpu::Batch`.
* However, for performance reasons, we have chosen not to do so. */
glBindVertexArray(0);
bind();
}
BLI_assert(ibo_id_ != 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, ibo_id_);
#ifndef NDEBUG
BLI_assert(binding < 16);
GLContext::get()->bound_ssbo_slots |= 1 << binding;
#endif
}
void GLIndexBuf::read(uint32_t *data) const
{
BLI_assert(is_active());
const void *buffer = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY);
memcpy(data, buffer, size_get());
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
}
bool GLIndexBuf::is_active() const
{
if (!ibo_id_) {
return false;
}
int active_ibo_id = 0;
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &active_ibo_id);
return ibo_id_ == active_ibo_id;
}
void GLIndexBuf::upload_data()
{
bind();
}
void GLIndexBuf::update_sub(uint start, uint len, const void *data)
{
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, start, len, data);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "GPU_index_buffer.hh"
#include <epoxy/gl.h>
namespace blender::gpu {
class GLIndexBuf : public IndexBuf {
friend class GLBatch;
friend class GLDrawList;
friend class GLShader; /* For compute shaders. */
private:
GLuint ibo_id_ = 0;
public:
~GLIndexBuf();
void bind();
void bind_as_ssbo(uint binding) override;
void read(uint32_t *data) const override;
void *offset_ptr(uint additional_vertex_offset) const
{
additional_vertex_offset += index_start_;
if (index_type_ == GPU_INDEX_U32) {
return reinterpret_cast<void *>(intptr_t(additional_vertex_offset) * sizeof(GLuint));
}
return reinterpret_cast<void *>(intptr_t(additional_vertex_offset) * sizeof(GLushort));
}
GLuint restart_index() const
{
return (index_type_ == GPU_INDEX_U16) ? 0xFFFFu : 0xFFFFFFFFu;
}
void upload_data() override;
void update_sub(uint start, uint len, const void *data) override;
private:
bool is_active() const;
void strip_restart_indices() override
{
/* No-op. */
}
MEM_CXX_CLASS_ALLOC_FUNCS("GLIndexBuf")
};
static inline GLenum to_gl(GPUIndexBufType type)
{
return (type == GPU_INDEX_U32) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Encapsulation of Frame-buffer states (attached textures, viewport, scissors).
*/
#pragma once
#include "BLI_assert.h"
#include "GPU_primitive.hh"
namespace blender::gpu {
static inline GLenum to_gl(GPUPrimType prim_type)
{
BLI_assert(prim_type != GPU_PRIM_NONE);
switch (prim_type) {
default:
case GPU_PRIM_POINTS:
return GL_POINTS;
case GPU_PRIM_LINES:
return GL_LINES;
case GPU_PRIM_LINE_STRIP:
return GL_LINE_STRIP;
case GPU_PRIM_LINE_LOOP:
return GL_LINE_LOOP;
case GPU_PRIM_TRIS:
return GL_TRIANGLES;
case GPU_PRIM_TRI_STRIP:
return GL_TRIANGLE_STRIP;
case GPU_PRIM_TRI_FAN:
return GL_TRIANGLE_FAN;
case GPU_PRIM_LINES_ADJ:
return GL_LINES_ADJACENCY;
case GPU_PRIM_LINE_STRIP_ADJ:
return GL_LINE_STRIP_ADJACENCY;
case GPU_PRIM_TRIS_ADJ:
return GL_TRIANGLES_ADJACENCY;
};
}
} // namespace blender::gpu

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "gl_query.hh"
namespace blender::gpu {
#define QUERY_CHUNCK_LEN 256
GLQueryPool::~GLQueryPool()
{
glDeleteQueries(query_ids_.size(), query_ids_.data());
}
void GLQueryPool::init(GPUQueryType type)
{
BLI_assert(initialized_ == false);
initialized_ = true;
type_ = type;
gl_type_ = to_gl(type);
query_issued_ = 0;
}
#if 0 /* TODO: to avoid realloc of permanent query pool. */
void GLQueryPool::reset(GPUQueryType type)
{
initialized_ = false;
}
#endif
void GLQueryPool::begin_query()
{
/* TODO: add assert about expected usage. */
while (query_issued_ >= query_ids_.size()) {
int64_t prev_size = query_ids_.size();
int64_t chunk_size = prev_size == 0 ? query_ids_.capacity() : QUERY_CHUNCK_LEN;
query_ids_.resize(prev_size + chunk_size);
glGenQueries(chunk_size, &query_ids_[prev_size]);
}
glBeginQuery(gl_type_, query_ids_[query_issued_++]);
}
void GLQueryPool::end_query()
{
/* TODO: add assert about expected usage. */
glEndQuery(gl_type_);
}
void GLQueryPool::get_occlusion_result(MutableSpan<uint32_t> r_values)
{
BLI_assert(r_values.size() == query_issued_);
for (int i = 0; i < query_issued_; i++) {
/* NOTE: This is a sync point. */
glGetQueryObjectuiv(query_ids_[i], GL_QUERY_RESULT, &r_values[i]);
}
}
} // namespace blender::gpu

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_vector.hh"
#include "gpu_query.hh"
#include <epoxy/gl.h>
namespace blender::gpu {
class GLQueryPool : public QueryPool {
private:
/** Contains queries object handles. */
Vector<GLuint, QUERY_MIN_LEN> query_ids_;
/** Type of this query pool. */
GPUQueryType type_;
/** Associated GL type. */
GLenum gl_type_;
/** Number of queries that have been issued since last initialization.
* Should be equal to query_ids_.size(). */
uint32_t query_issued_;
/** Can only be initialized once. */
bool initialized_ = false;
public:
~GLQueryPool();
void init(GPUQueryType type) override;
void begin_query() override;
void end_query() override;
void get_occlusion_result(MutableSpan<uint32_t> r_values) override;
};
static inline GLenum to_gl(GPUQueryType type)
{
if (type == GPU_QUERY_OCCLUSION) {
/* TODO(fclem): try with GL_ANY_SAMPLES_PASSED. */
return GL_SAMPLES_PASSED;
}
BLI_assert(0);
return GL_SAMPLES_PASSED;
}
} // namespace blender::gpu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,291 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include <epoxy/gl.h>
#include "BLI_map.hh"
#include "BLI_subprocess.hh"
#include "BLI_utility_mixins.hh"
#include "GPU_capabilities.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_shader_private.hh"
#include <functional>
#include <mutex>
namespace blender::gpu {
/**
* Shaders that uses specialization constants must keep track of the sources in order to rebuild
* shader stages.
*
* Some sources are shared and won't be copied. For example for dependencies. In this case we
* would only store the source_ref.
*
* Other sources would be stored in the #source attribute. #source_ref
* would still be updated.
*/
struct GLSource {
std::string source;
std::optional<StringRefNull> source_ref;
GLSource() = default;
GLSource(StringRefNull other_source);
};
class GLSources : public Vector<GLSource> {
public:
GLSources &operator=(Span<StringRefNull> other);
Vector<StringRefNull> sources_get() const;
std::string to_string() const;
};
/**
* The full sources for each shader stage, baked into a single string from their respective
* GLSources. (Can be retrieved from GLShader::get_sources())
*/
struct GLSourcesBaked : NonCopyable {
std::string comp;
std::string vert;
std::string geom;
std::string frag;
/* Returns the size (in bytes) required to store the source of all the used stages. */
size_t size();
};
/**
* Implementation of shader compilation and uniforms handling using OpenGL.
*/
class GLShader : public Shader {
friend shader::ShaderCreateInfo;
friend shader::StageInterfaceInfo;
friend class GLSubprocessShaderCompiler;
friend class GLShaderCompiler;
private:
struct GLProgram {
/** Handle for program. */
GLuint program_id = 0;
/** Handle for individual shader stages. */
GLuint vert_shader = 0;
GLuint geom_shader = 0;
GLuint frag_shader = 0;
GLuint compute_shader = 0;
std::mutex compilation_mutex;
GLProgram() {}
~GLProgram();
void program_link(StringRefNull shader_name);
};
using GLProgramCacheKey = Vector<shader::SpecializationConstant::Value>;
/** Contains all specialized shader variants. */
Map<GLProgramCacheKey, std::unique_ptr<GLProgram>> program_cache_;
std::mutex program_cache_mutex_;
/** Main program instance. This is the default specialized variant that is first compiled. */
GLProgram *main_program_ = nullptr;
/* When true, the shader generates its GLSources but it's not compiled.
* (Used for subprocess compilation) */
bool is_codegen_only_ = false;
/**
* When the shader uses Specialization Constants these attribute contains the sources to
* rebuild shader stages. When Specialization Constants aren't used they are empty to
* reduce memory needs.
*/
GLSources vertex_sources_;
GLSources geometry_sources_;
GLSources fragment_sources_;
GLSources compute_sources_;
Vector<const char *> specialization_constant_names_;
void update_program_and_sources(GLSources &stage_sources, MutableSpan<StringRefNull> sources);
/**
* Return a GLProgram that reflects the given `constants_state`.
* The returned program_id is in linked state, or an error happened during linking.
*/
GLShader::GLProgram &program_get(const shader::SpecializationConstants *constants_state);
/** True if any shader failed to compile. */
bool compilation_failed_ = false;
std::string debug_source;
public:
GLShader(const char *name);
~GLShader();
void init(const shader::ShaderCreateInfo &info, bool is_codegen_only) override;
const shader::ShaderCreateInfo &patch_create_info(
const shader::ShaderCreateInfo &original_info) override
{
return original_info;
}
/** Return true on success. */
void vertex_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) override;
void geometry_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) override;
void fragment_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) override;
void compute_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) override;
bool finalize(const shader::ShaderCreateInfo *info = nullptr) override;
bool post_finalize(const shader::ShaderCreateInfo *info = nullptr);
void warm_cache(int /*limit*/) override {};
std::string resources_declare(const shader::ShaderCreateInfo &info) const override;
std::string constants_declare(const shader::SpecializationConstants &constants_state) const;
std::string vertex_interface_declare(const shader::ShaderCreateInfo &info) const override;
std::string fragment_interface_declare(const shader::ShaderCreateInfo &info) const override;
std::string geometry_interface_declare(const shader::ShaderCreateInfo &info) const override;
std::string geometry_layout_declare(const shader::ShaderCreateInfo &info) const override;
std::string compute_layout_declare(const shader::ShaderCreateInfo &info) const override;
void bind(const shader::SpecializationConstants *constants_state) override;
void unbind() override;
void uniform_float(int location, int comp_len, int array_size, const float *data) override;
void uniform_int(int location, int comp_len, int array_size, const int *data) override;
bool is_compute() const
{
if (!vertex_sources_.is_empty()) {
return false;
}
if (!compute_sources_.is_empty()) {
return true;
}
return main_program_->compute_shader != 0;
}
GLSourcesBaked get_sources();
private:
StringRefNull glsl_patch_get(GLenum gl_stage);
bool has_specialization_constants() const
{
return constants->types.is_empty() == false;
}
/** Create, compile and attach the shader stage to the shader program. */
GLuint create_shader_stage(GLenum gl_stage,
MutableSpan<StringRefNull> sources,
GLSources &gl_sources,
const shader::SpecializationConstants &constants_state);
/**
* \brief features available on newer implementation such as native barycentric coordinates
* and layered rendering, necessitate a geometry shader to work on older hardware.
*/
std::string workaround_geometry_shader_source_create(const shader::ShaderCreateInfo &info);
bool do_geometry_shader_injection(const shader::ShaderCreateInfo *info) const;
MEM_CXX_CLASS_ALLOC_FUNCS("GLShader");
};
class GLShaderCompiler : public ShaderCompiler {
public:
GLShaderCompiler()
: ShaderCompiler(GPU_max_parallel_compilations(), GPUWorker::ContextType::PerThread, true) {
};
virtual void specialize_shader(const ShaderSpecialization &specialization) override;
};
#if BLI_SUBPROCESS_SUPPORT
class GLCompilerWorker {
friend class GLSubprocessShaderCompiler;
private:
BlenderSubprocess subprocess_;
std::unique_ptr<SharedMemory> shared_mem_;
std::unique_ptr<SharedSemaphore> start_semaphore_;
std::unique_ptr<SharedSemaphore> end_semaphore_;
std::unique_ptr<SharedSemaphore> close_semaphore_;
enum State {
/* The worker has been acquired and the compilation has been requested. */
COMPILATION_REQUESTED,
/* The shader binary result is ready to be read. */
COMPILATION_READY,
/* The binary result has been loaded into a program and the worker can be released. */
COMPILATION_FINISHED,
/* The worker is not currently in use and can be acquired. */
AVAILABLE
};
std::atomic<State> state_ = AVAILABLE;
double compilation_start = 0;
GLCompilerWorker();
~GLCompilerWorker();
void compile(const GLSourcesBaked &sources);
bool block_until_ready();
bool load_program_binary(GLint program);
void release();
/* Check if the process may have closed/crashed/hanged. */
bool is_lost();
};
class GLSubprocessShaderCompiler : public ShaderCompiler {
private:
Vector<GLCompilerWorker *> workers_;
std::mutex workers_mutex_;
GLCompilerWorker *get_compiler_worker();
GLShader::GLProgram *specialization_program_get(ShaderSpecialization &specialization);
public:
GLSubprocessShaderCompiler()
: ShaderCompiler(GPU_max_parallel_compilations(), GPUWorker::ContextType::PerThread, true) {
};
virtual ~GLSubprocessShaderCompiler() override;
virtual Shader *compile_shader(const shader::ShaderCreateInfo &info) override;
virtual void specialize_shader(const ShaderSpecialization &specialization) override;
};
#else
class GLSubprocessShaderCompiler : public ShaderCompiler {};
#endif
class GLLogParser : public GPULogParser {
public:
const char *parse_line(const char *source_combined,
const char *log_line,
GPULogItem &log_item) override;
protected:
const char *skip_severity_prefix(const char *log_line, GPULogItem &log_item);
const char *skip_severity_keyword(const char *log_line, GPULogItem &log_item);
MEM_CXX_CLASS_ALLOC_FUNCS("GLLogParser");
};
} // namespace blender::gpu

View File

@@ -0,0 +1,592 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU shader interface (C --> GLSL)
*/
#include "BLI_bitmap.h"
#include "gl_batch.hh"
#include "gl_context.hh"
#include "gl_shader_interface.hh"
#include "GPU_capabilities.hh"
namespace blender {
using namespace blender::gpu::shader;
namespace gpu {
/* -------------------------------------------------------------------- */
/** \name Binding assignment
*
* To mimic vulkan, we assign binding at shader creation to avoid shader recompilation.
* In the future, we should set it in the shader using layout(binding = i) and query its value.
* \{ */
static inline int block_binding(int32_t program, uint32_t block_index)
{
/* For now just assign a consecutive index. In the future, we should set it in
* the shader using layout(binding = i) and query its value. */
glUniformBlockBinding(program, block_index, block_index);
return block_index;
}
static inline int sampler_binding(int32_t program,
uint32_t uniform_index,
int32_t uniform_location,
int *sampler_len)
{
/* Identify sampler uniforms and assign sampler units to them. */
GLint type;
glGetActiveUniformsiv(program, 1, &uniform_index, GL_UNIFORM_TYPE, &type);
switch (type) {
case GL_SAMPLER_1D:
case GL_SAMPLER_2D:
case GL_SAMPLER_3D:
case GL_SAMPLER_CUBE:
case GL_SAMPLER_CUBE_MAP_ARRAY_ARB: /* OpenGL 4.0 */
case GL_SAMPLER_1D_SHADOW:
case GL_SAMPLER_2D_SHADOW:
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_2D_ARRAY:
case GL_SAMPLER_1D_ARRAY_SHADOW:
case GL_SAMPLER_2D_ARRAY_SHADOW:
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_SAMPLER_CUBE_SHADOW:
case GL_SAMPLER_BUFFER:
case GL_INT_SAMPLER_1D:
case GL_INT_SAMPLER_2D:
case GL_INT_SAMPLER_3D:
case GL_INT_SAMPLER_CUBE:
case GL_INT_SAMPLER_1D_ARRAY:
case GL_INT_SAMPLER_2D_ARRAY:
case GL_INT_SAMPLER_2D_MULTISAMPLE:
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_INT_SAMPLER_BUFFER:
case GL_UNSIGNED_INT_SAMPLER_1D:
case GL_UNSIGNED_INT_SAMPLER_2D:
case GL_UNSIGNED_INT_SAMPLER_3D:
case GL_UNSIGNED_INT_SAMPLER_CUBE:
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_BUFFER: {
/* For now just assign a consecutive index. In the future, we should set it in
* the shader using layout(binding = i) and query its value. */
int binding = *sampler_len;
glUniform1i(uniform_location, binding);
(*sampler_len)++;
return binding;
}
default:
return -1;
}
}
static inline int image_binding(int32_t program,
uint32_t uniform_index,
int32_t uniform_location,
int *image_len)
{
/* Identify image uniforms and assign image units to them. */
GLint type;
glGetActiveUniformsiv(program, 1, &uniform_index, GL_UNIFORM_TYPE, &type);
switch (type) {
case GL_IMAGE_1D:
case GL_IMAGE_2D:
case GL_IMAGE_3D:
case GL_IMAGE_CUBE:
case GL_IMAGE_BUFFER:
case GL_IMAGE_1D_ARRAY:
case GL_IMAGE_2D_ARRAY:
case GL_IMAGE_CUBE_MAP_ARRAY:
case GL_INT_IMAGE_1D:
case GL_INT_IMAGE_2D:
case GL_INT_IMAGE_3D:
case GL_INT_IMAGE_CUBE:
case GL_INT_IMAGE_BUFFER:
case GL_INT_IMAGE_1D_ARRAY:
case GL_INT_IMAGE_2D_ARRAY:
case GL_INT_IMAGE_CUBE_MAP_ARRAY:
case GL_UNSIGNED_INT_IMAGE_1D:
case GL_UNSIGNED_INT_IMAGE_2D:
case GL_UNSIGNED_INT_IMAGE_3D:
case GL_UNSIGNED_INT_IMAGE_CUBE:
case GL_UNSIGNED_INT_IMAGE_BUFFER:
case GL_UNSIGNED_INT_IMAGE_1D_ARRAY:
case GL_UNSIGNED_INT_IMAGE_2D_ARRAY:
case GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY: {
/* For now just assign a consecutive index. In the future, we should set it in
* the shader using layout(binding = i) and query its value. */
int binding = *image_len;
glUniform1i(uniform_location, binding);
(*image_len)++;
return binding;
}
default:
return -1;
}
}
static inline int ssbo_binding(int32_t program, uint32_t ssbo_index)
{
GLint binding = -1;
GLenum property = GL_BUFFER_BINDING;
GLint values_written = 0;
glGetProgramResourceiv(
program, GL_SHADER_STORAGE_BLOCK, ssbo_index, 1, &property, 1, &values_written, &binding);
return binding;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Creation / Destruction
* \{ */
static Type gpu_type_from_gl_type(int gl_type)
{
switch (gl_type) {
case GL_FLOAT:
return Type::float_t;
case GL_FLOAT_VEC2:
return Type::float2_t;
case GL_FLOAT_VEC3:
return Type::float3_t;
case GL_FLOAT_VEC4:
return Type::float4_t;
case GL_FLOAT_MAT3:
return Type::float3x3_t;
case GL_FLOAT_MAT4:
return Type::float4x4_t;
case GL_UNSIGNED_INT:
return Type::uint_t;
case GL_UNSIGNED_INT_VEC2:
return Type::uint2_t;
case GL_UNSIGNED_INT_VEC3:
return Type::uint3_t;
case GL_UNSIGNED_INT_VEC4:
return Type::uint4_t;
case GL_INT:
return Type::int_t;
case GL_INT_VEC2:
return Type::int2_t;
case GL_INT_VEC3:
return Type::int3_t;
case GL_INT_VEC4:
return Type::int4_t;
case GL_BOOL:
return Type::bool_t;
case GL_FLOAT_MAT2:
case GL_FLOAT_MAT2x3:
case GL_FLOAT_MAT2x4:
case GL_FLOAT_MAT3x2:
case GL_FLOAT_MAT3x4:
case GL_FLOAT_MAT4x2:
case GL_FLOAT_MAT4x3:
default:
BLI_assert(0);
}
return Type::float_t;
}
GLShaderInterface::GLShaderInterface(GLuint program)
{
GLuint last_program;
glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<GLint *>(&last_program));
/* Necessary to make #glUniform works. */
glUseProgram(program);
GLint max_attr_name_len = 0, attr_len = 0;
glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_attr_name_len);
glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attr_len);
GLint max_ubo_name_len = 0, ubo_len = 0;
glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &max_ubo_name_len);
glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &ubo_len);
GLint max_uniform_name_len = 0, active_uniform_len = 0, uniform_len = 0;
glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_uniform_name_len);
glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniform_len);
uniform_len = active_uniform_len;
GLint max_ssbo_name_len = 0, ssbo_len = 0;
glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &ssbo_len);
glGetProgramInterfaceiv(
program, GL_SHADER_STORAGE_BLOCK, GL_MAX_NAME_LENGTH, &max_ssbo_name_len);
BLI_assert_msg(ubo_len <= 16, "enabled_ubo_mask_ is uint16_t");
/* Work around driver bug with Intel HD 4600 on Windows 7/8, where
* GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH does not work. */
if (attr_len > 0 && max_attr_name_len == 0) {
max_attr_name_len = 256;
}
if (ubo_len > 0 && max_ubo_name_len == 0) {
max_ubo_name_len = 256;
}
if (uniform_len > 0 && max_uniform_name_len == 0) {
max_uniform_name_len = 256;
}
if (ssbo_len > 0 && max_ssbo_name_len == 0) {
max_ssbo_name_len = 256;
}
/* GL_ACTIVE_UNIFORMS lied to us! Remove the UBO uniforms from the total before
* allocating the uniform array. */
GLint max_ubo_uni_len = 0;
for (int i = 0; i < ubo_len; i++) {
GLint ubo_uni_len;
glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &ubo_uni_len);
max_ubo_uni_len = max_ii(max_ubo_uni_len, ubo_uni_len);
uniform_len -= ubo_uni_len;
}
/* Bit set to true if uniform comes from a uniform block. */
BLI_bitmap *uniforms_from_blocks = BLI_BITMAP_NEW(active_uniform_len, __func__);
/* Set uniforms from block for exclusion. */
GLint *ubo_uni_ids = MEM_new_array_uninitialized<GLint>(max_ubo_uni_len, __func__);
for (int i = 0; i < ubo_len; i++) {
GLint ubo_uni_len;
glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &ubo_uni_len);
glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, ubo_uni_ids);
for (int u = 0; u < ubo_uni_len; u++) {
BLI_BITMAP_ENABLE(uniforms_from_blocks, ubo_uni_ids[u]);
}
}
MEM_delete(ubo_uni_ids);
int input_tot_len = attr_len + ubo_len + uniform_len + ssbo_len;
inputs_ = MEM_new_array_zeroed<ShaderInput>(input_tot_len, __func__);
const uint32_t name_buffer_len = attr_len * max_attr_name_len + ubo_len * max_ubo_name_len +
uniform_len * max_uniform_name_len +
ssbo_len * max_ssbo_name_len;
name_buffer_ = MEM_new_array_uninitialized<char>(name_buffer_len, "name_buffer");
uint32_t name_buffer_offset = 0;
/* Attributes */
enabled_attr_mask_ = 0;
for (int i = 0; i < attr_len; i++) {
char *name = name_buffer_ + name_buffer_offset;
GLsizei remaining_buffer = name_buffer_len - name_buffer_offset;
GLsizei name_len = 0;
GLenum type;
GLint size;
glGetActiveAttrib(program, i, remaining_buffer, &name_len, &size, &type, name);
GLint location = glGetAttribLocation(program, name);
/* Ignore OpenGL names like `gl_BaseInstanceARB`, `gl_InstanceID` and `gl_VertexID`. */
if (location == -1) {
continue;
}
ShaderInput *input = &inputs_[attr_len_++];
input->location = input->binding = location;
name_buffer_offset += set_input_name(input, name, name_len);
enabled_attr_mask_ |= (1 << input->location);
/* Used in `GPU_shader_get_attribute_info`. */
attr_types_[input->location] = uint8_t(gpu_type_from_gl_type(type));
}
/* Uniform Blocks */
for (int i = 0; i < ubo_len; i++) {
char *name = name_buffer_ + name_buffer_offset;
GLsizei remaining_buffer = name_buffer_len - name_buffer_offset;
GLsizei name_len = 0;
glGetActiveUniformBlockName(program, i, remaining_buffer, &name_len, name);
ShaderInput *input = &inputs_[attr_len_ + ubo_len_++];
input->binding = input->location = block_binding(program, i);
name_buffer_offset += this->set_input_name(input, name, name_len);
enabled_ubo_mask_ |= (1 << input->binding);
}
/* Uniforms & samplers & images */
for (int i = 0, sampler = 0, image = 0; i < active_uniform_len; i++) {
if (BLI_BITMAP_TEST(uniforms_from_blocks, i)) {
continue;
}
char *name = name_buffer_ + name_buffer_offset;
GLsizei remaining_buffer = name_buffer_len - name_buffer_offset;
GLsizei name_len = 0;
glGetActiveUniformName(program, i, remaining_buffer, &name_len, name);
ShaderInput *input = &inputs_[attr_len_ + ubo_len_ + uniform_len_++];
input->location = glGetUniformLocation(program, name);
input->binding = sampler_binding(program, i, input->location, &sampler);
name_buffer_offset += this->set_input_name(input, name, name_len);
enabled_tex_mask_ |= (input->binding != -1) ? (1lu << input->binding) : 0lu;
if (input->binding == -1) {
input->binding = image_binding(program, i, input->location, &image);
enabled_ima_mask_ |= (input->binding != -1) ? (1lu << input->binding) : 0lu;
}
}
/* SSBOs */
for (int i = 0; i < ssbo_len; i++) {
char *name = name_buffer_ + name_buffer_offset;
GLsizei remaining_buffer = name_buffer_len - name_buffer_offset;
GLsizei name_len = 0;
glGetProgramResourceName(
program, GL_SHADER_STORAGE_BLOCK, i, remaining_buffer, &name_len, name);
const GLint binding = ssbo_binding(program, i);
ShaderInput *input = &inputs_[attr_len_ + ubo_len_ + uniform_len_ + ssbo_len_++];
input->binding = input->location = binding;
name_buffer_offset += this->set_input_name(input, name, name_len);
enabled_ssbo_mask_ |= (input->binding != -1) ? (1lu << input->binding) : 0lu;
}
/* Builtin Uniforms */
for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORMS; u_int++) {
GPUUniformBuiltin u = static_cast<GPUUniformBuiltin>(u_int);
builtins_[u] = glGetUniformLocation(program, builtin_uniform_name(u));
}
/* Builtin Uniforms Blocks */
for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORM_BLOCKS; u_int++) {
GPUUniformBlockBuiltin u = static_cast<GPUUniformBlockBuiltin>(u_int);
const ShaderInput *block = this->ubo_get(builtin_uniform_block_name(u));
builtin_blocks_[u] = (block != nullptr) ? block->binding : -1;
}
MEM_delete(uniforms_from_blocks);
/* Resize name buffer to save some memory. */
if (name_buffer_offset < name_buffer_len) {
name_buffer_ = static_cast<char *>(
MEM_realloc_uninitialized(name_buffer_, name_buffer_offset));
}
// this->debug_print();
this->sort_inputs();
glUseProgram(last_program);
}
GLShaderInterface::GLShaderInterface(GLuint program, const shader::ShaderCreateInfo &info)
{
using namespace blender::gpu::shader;
attr_len_ = info.vertex_inputs_.size();
uniform_len_ = info.push_constants_.size();
constant_len_ = info.specialization_constants_.size();
ubo_len_ = 0;
ssbo_len_ = 0;
Vector<ShaderCreateInfo::Resource> all_resources = info.resources_get_all_();
for (ShaderCreateInfo::Resource &res : all_resources) {
switch (res.bind_type) {
case ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
ubo_len_++;
break;
case ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
ssbo_len_++;
break;
case ShaderCreateInfo::Resource::BindType::SAMPLER:
uniform_len_++;
break;
case ShaderCreateInfo::Resource::BindType::IMAGE:
uniform_len_++;
break;
}
}
BLI_assert_msg(ubo_len_ <= 16, "enabled_ubo_mask_ is uint16_t");
int input_tot_len = attr_len_ + ubo_len_ + uniform_len_ + ssbo_len_ + constant_len_;
inputs_ = MEM_new_array_zeroed<ShaderInput>(input_tot_len, __func__);
ShaderInput *input = inputs_;
name_buffer_ = MEM_new_array_uninitialized<char>(info.interface_names_size_, "name_buffer");
uint32_t name_buffer_offset = 0;
/* Necessary to make #glUniform works. TODO(fclem) Remove. */
GLuint last_program;
glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<GLint *>(&last_program));
glUseProgram(program);
/* Attributes */
for (const ShaderCreateInfo::VertIn &attr : info.vertex_inputs_) {
copy_input_name(input, attr.name, name_buffer_, name_buffer_offset);
if (true || !GLContext::explicit_location_support) {
input->location = input->binding = glGetAttribLocation(program, attr.name.c_str());
}
else {
input->location = input->binding = attr.index;
}
if (input->location != -1) {
enabled_attr_mask_ |= (1 << input->location);
/* Used in `GPU_shader_get_attribute_info`. */
attr_types_[input->location] = uint8_t(attr.type);
}
input++;
}
/* Uniform Blocks */
for (const ShaderCreateInfo::Resource &res : all_resources) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER) {
copy_input_name(input, res.uniformbuf.name, name_buffer_, name_buffer_offset);
input->location = input->binding = res.slot;
enabled_ubo_mask_ |= (1 << input->binding);
input++;
}
}
/* Uniforms & samplers & images */
for (const ShaderCreateInfo::Resource &res : all_resources) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::SAMPLER) {
copy_input_name(input, res.sampler.name, name_buffer_, name_buffer_offset);
/* Until we make use of explicit uniform location or eliminate all
* sampler manually changing. */
if (true || !GLContext::explicit_location_support) {
input->location = glGetUniformLocation(program, res.sampler.name.c_str());
glUniform1i(input->location, res.slot);
}
input->binding = res.slot;
enabled_tex_mask_ |= (1ull << input->binding);
input++;
}
else if (res.bind_type == ShaderCreateInfo::Resource::BindType::IMAGE) {
copy_input_name(input, res.image.name, name_buffer_, name_buffer_offset);
/* Until we make use of explicit uniform location. */
if (true || !GLContext::explicit_location_support) {
input->location = glGetUniformLocation(program, res.image.name.c_str());
glUniform1i(input->location, res.slot);
}
input->binding = res.slot;
enabled_ima_mask_ |= (1 << input->binding);
input++;
}
}
for (const ShaderCreateInfo::PushConst &uni : info.push_constants_) {
copy_input_name(input, uni.name, name_buffer_, name_buffer_offset);
input->location = glGetUniformLocation(program, name_buffer_ + input->name_offset);
input->binding = -1;
input++;
}
set_image_formats_from_info(info);
/* SSBOs */
for (const ShaderCreateInfo::Resource &res : all_resources) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER) {
copy_input_name(input, res.storagebuf.name, name_buffer_, name_buffer_offset);
input->location = input->binding = res.slot;
enabled_ssbo_mask_ |= (1 << input->binding);
input++;
}
}
for (const ShaderCreateInfo::Resource &res : info.geometry_resources_) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER) {
ssbo_attr_mask_ |= (1 << res.slot);
}
else {
BLI_assert_msg(0, "Resource type is not supported for Geometry frequency");
}
}
/* Constants */
int constant_id = 0;
for (const SpecializationConstant &constant : info.specialization_constants_) {
copy_input_name(input, constant.name, name_buffer_, name_buffer_offset);
input->location = constant_id++;
input++;
}
this->sort_inputs();
/* Resolving builtins must happen after the inputs have been sorted. */
/* Builtin Uniforms */
for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORMS; u_int++) {
GPUUniformBuiltin u = static_cast<GPUUniformBuiltin>(u_int);
const ShaderInput *uni = this->uniform_get(builtin_uniform_name(u));
builtins_[u] = (uni != nullptr) ? uni->location : -1;
}
/* Builtin Uniforms Blocks */
for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORM_BLOCKS; u_int++) {
GPUUniformBlockBuiltin u = static_cast<GPUUniformBlockBuiltin>(u_int);
const ShaderInput *block = this->ubo_get(builtin_uniform_block_name(u));
builtin_blocks_[u] = (block != nullptr) ? block->binding : -1;
}
// this->debug_print();
glUseProgram(last_program);
}
GLShaderInterface::~GLShaderInterface()
{
for (auto *ref : refs_) {
if (ref != nullptr) {
ref->remove(this);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Batch Reference
* \{ */
void GLShaderInterface::ref_add(GLVaoCache *ref)
{
for (int i = 0; i < refs_.size(); i++) {
if (refs_[i] == nullptr) {
refs_[i] = ref;
return;
}
}
refs_.append(ref);
}
void GLShaderInterface::ref_remove(GLVaoCache *ref)
{
for (int i = 0; i < refs_.size(); i++) {
if (refs_[i] == ref) {
refs_[i] = nullptr;
break; /* cannot have duplicates */
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Validation
* TODO
* \{ */
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU shader interface (C --> GLSL)
*
* Structure detailing needed vertex inputs and resources for a specific shader.
* A shader interface can be shared between two similar shaders.
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "BLI_vector.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_shader_interface.hh"
namespace blender::gpu {
class GLVaoCache;
/**
* Implementation of Shader interface using OpenGL.
*/
class GLShaderInterface : public ShaderInterface {
private:
/** Reference to VaoCaches using this interface */
Vector<GLVaoCache *> refs_;
public:
GLShaderInterface(GLuint program, const shader::ShaderCreateInfo &info);
GLShaderInterface(GLuint program);
~GLShaderInterface();
void ref_add(GLVaoCache *ref);
void ref_remove(GLVaoCache *ref);
MEM_CXX_CLASS_ALLOC_FUNCS("GLShaderInterface");
};
} // namespace blender::gpu

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "gl_shader.hh"
#include "GPU_platform.hh"
namespace blender::gpu {
const char *GLLogParser::parse_line(const char *source_combined,
const char *log_line,
GPULogItem &log_item)
{
/* Skip ERROR: or WARNING:. */
log_line = skip_severity_prefix(log_line, log_item);
log_line = skip_separators(log_line, "(: ");
/* Parse error line & char numbers. */
if (at_number(log_line)) {
const char *error_line_number_end;
log_item.cursor.row = parse_number(log_line, &error_line_number_end);
/* Try to fetch the error character (not always available). */
if (at_any(error_line_number_end, "(:") && at_number(&error_line_number_end[1])) {
log_item.cursor.column = parse_number(error_line_number_end + 1, &log_line);
}
else {
log_line = error_line_number_end;
}
/* There can be a 3rd number (case of mesa driver). */
if (at_any(log_line, "(:") && at_number(&log_line[1])) {
log_item.cursor.source = log_item.cursor.row;
log_item.cursor.row = log_item.cursor.column;
log_item.cursor.column = parse_number(log_line + 1, &error_line_number_end);
log_line = error_line_number_end;
}
}
if ((log_item.cursor.row != -1) && (log_item.cursor.column != -1)) {
if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
/* source:row */
log_item.cursor.source = log_item.cursor.row;
log_item.cursor.row = log_item.cursor.column;
log_item.cursor.column = -1;
}
else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OFFICIAL)) {
/* source:row */
log_item.cursor.source = log_item.cursor.row;
log_item.cursor.row = log_item.cursor.column;
log_item.cursor.column = -1;
}
else {
/* line:char */
}
}
if (log_item.cursor.row != -1) {
/* Get to the wanted line. */
size_t line_start_character = line_start_get(source_combined, log_item.cursor.row);
if (line_start_character != -1) {
StringRef filename = filename_get(source_combined, line_start_character);
size_t line_number = source_line_get(source_combined, line_start_character);
log_item.cursor.file_name_and_error_line = std::string(filename) + ':' +
std::to_string(line_number);
if (log_item.cursor.column != -1) {
log_item.cursor.file_name_and_error_line += ':' +
std::to_string(log_item.cursor.column + 1);
}
}
}
log_line = skip_separators(log_line, ":) ");
/* Skip to message. Avoid redundant info. */
log_line = skip_severity_keyword(log_line, log_item);
log_line = skip_separators(log_line, ":) ");
return log_line;
}
const char *GLLogParser::skip_severity_prefix(const char *log_line, GPULogItem &log_item)
{
return skip_severity(log_line, log_item, "ERROR", "WARNING", "NOTE");
}
const char *GLLogParser::skip_severity_keyword(const char *log_line, GPULogItem &log_item)
{
return skip_severity(log_line, log_item, "error", "warning", "note");
}
} // namespace blender::gpu

View File

@@ -0,0 +1,685 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BKE_global.hh"
#include "BLI_math_base.h"
#include "BLI_math_bits.h"
#include "GPU_capabilities.hh"
#include "gl_context.hh"
#include "gl_framebuffer.hh"
#include "gl_texture.hh"
#include "gl_state.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name GLStateManager
* \{ */
GLStateManager::GLStateManager()
{
/* Set other states that never change. */
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
glEnable(GL_MULTISAMPLE);
glDisable(GL_DITHER);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
/* Takes precedence over #GL_PRIMITIVE_RESTART.
* Sets restart index correctly following the IBO type. */
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
/* Limits. */
glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, line_width_range_);
/* Force update using default state. */
current_ = ~state;
current_mutable_ = ~mutable_state;
set_state(state);
set_mutable_state(mutable_state);
}
void GLStateManager::apply_state()
{
this->set_state(this->state);
this->set_mutable_state(this->mutable_state);
this->texture_bind_apply();
this->image_bind_apply();
/* This is needed by gpu_py_offscreen. */
active_fb->apply_state();
};
void GLStateManager::force_state()
{
/* Little exception for clip distances since they need to keep the old count correct. */
uint32_t clip_distances = current_.clip_distances;
current_ = ~this->state;
current_.clip_distances = clip_distances;
current_mutable_ = ~this->mutable_state;
this->set_state(this->state);
this->set_mutable_state(this->mutable_state);
};
void GLStateManager::set_state(const GPUState &state)
{
GPUState changed = state ^ current_;
if (changed.blend != 0) {
set_blend(GPUBlend(state.blend));
}
if (changed.write_mask != 0) {
set_write_mask(GPUWriteMask(state.write_mask));
}
if (changed.depth_test != 0) {
set_depth_test(GPUDepthTest(state.depth_test));
}
if (changed.stencil_test != 0 || changed.stencil_op != 0) {
set_stencil_test(GPUStencilTest(state.stencil_test), GPUStencilOp(state.stencil_op));
set_stencil_mask(GPUStencilTest(state.stencil_test), mutable_state);
}
if (changed.clip_distances != 0) {
set_clip_distances(state.clip_distances, current_.clip_distances);
}
if (changed.culling_test != 0) {
set_backface_culling(GPUFaceCullTest(state.culling_test));
}
if (changed.logic_op_xor != 0) {
set_logic_op(state.logic_op_xor);
}
if (changed.invert_facing != 0) {
set_facing(state.invert_facing);
}
if (changed.provoking_vert != 0) {
set_provoking_vert(GPUProvokingVertex(state.provoking_vert));
}
if (changed.clip_control != 0) {
set_clip_control(state.clip_control);
}
/* TODO: remove. */
if (changed.polygon_smooth) {
if (state.polygon_smooth) {
glEnable(GL_POLYGON_SMOOTH);
}
else {
glDisable(GL_POLYGON_SMOOTH);
}
}
if (changed.line_smooth) {
if (state.line_smooth) {
glEnable(GL_LINE_SMOOTH);
}
else {
glDisable(GL_LINE_SMOOTH);
}
}
current_ = state;
}
void GLStateManager::set_mutable_state(const GPUStateMutable &state)
{
GPUStateMutable changed = state ^ current_mutable_;
/* TODO: remove, should be uniform. */
if (float_as_uint(changed.point_size) != 0) {
if (state.point_size > 0.0f) {
glEnable(GL_PROGRAM_POINT_SIZE);
}
else {
glDisable(GL_PROGRAM_POINT_SIZE);
glPointSize(fabsf(state.point_size));
}
}
if (float_as_uint(changed.line_width) != 0) {
/* TODO: remove, should use wide line shader. */
glLineWidth(clamp_f(state.line_width, line_width_range_[0], line_width_range_[1]));
}
if (changed.stencil_compare_mask != 0 || changed.stencil_reference != 0 ||
changed.stencil_write_mask != 0)
{
set_stencil_mask(GPUStencilTest(current_.stencil_test), state);
}
current_mutable_ = state;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name State set functions
* \{ */
void GLStateManager::set_write_mask(const GPUWriteMask value)
{
glDepthMask((value & GPU_WRITE_DEPTH) != 0);
glColorMask((value & GPU_WRITE_RED) != 0,
(value & GPU_WRITE_GREEN) != 0,
(value & GPU_WRITE_BLUE) != 0,
(value & GPU_WRITE_ALPHA) != 0);
if (value == GPU_WRITE_NONE) {
glEnable(GL_RASTERIZER_DISCARD);
}
else {
glDisable(GL_RASTERIZER_DISCARD);
}
}
void GLStateManager::set_depth_test(const GPUDepthTest value)
{
GLenum func;
switch (value) {
case GPU_DEPTH_LESS:
func = GL_LESS;
break;
case GPU_DEPTH_LESS_EQUAL:
func = GL_LEQUAL;
break;
case GPU_DEPTH_EQUAL:
func = GL_EQUAL;
break;
case GPU_DEPTH_GREATER:
func = GL_GREATER;
break;
case GPU_DEPTH_GREATER_EQUAL:
func = GL_GEQUAL;
break;
case GPU_DEPTH_ALWAYS:
default:
func = GL_ALWAYS;
break;
}
if (value != GPU_DEPTH_NONE) {
glEnable(GL_DEPTH_TEST);
glDepthFunc(func);
}
else {
glDisable(GL_DEPTH_TEST);
}
}
void GLStateManager::set_stencil_test(const GPUStencilTest test, const GPUStencilOp operation)
{
switch (operation) {
case GPU_STENCIL_OP_REPLACE:
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
break;
case GPU_STENCIL_OP_COUNT_DEPTH_PASS:
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
break;
case GPU_STENCIL_OP_COUNT_DEPTH_FAIL:
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_KEEP);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
break;
case GPU_STENCIL_OP_NONE:
default:
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}
if (test != GPU_STENCIL_NONE) {
glEnable(GL_STENCIL_TEST);
}
else {
glDisable(GL_STENCIL_TEST);
}
}
void GLStateManager::set_stencil_mask(const GPUStencilTest test, const GPUStateMutable &state)
{
GLenum func;
switch (test) {
case GPU_STENCIL_NEQUAL:
func = GL_NOTEQUAL;
break;
case GPU_STENCIL_EQUAL:
func = GL_EQUAL;
break;
case GPU_STENCIL_ALWAYS:
func = GL_ALWAYS;
break;
case GPU_STENCIL_NONE:
default:
glStencilMask(0x00);
glStencilFunc(GL_ALWAYS, 0x00, 0x00);
return;
}
glStencilMask(state.stencil_write_mask);
glStencilFunc(func, state.stencil_reference, state.stencil_compare_mask);
}
void GLStateManager::set_clip_distances(const int new_dist_len, const int old_dist_len)
{
for (int i = 0; i < new_dist_len; i++) {
glEnable(GL_CLIP_DISTANCE0 + i);
}
for (int i = new_dist_len; i < old_dist_len; i++) {
glDisable(GL_CLIP_DISTANCE0 + i);
}
}
void GLStateManager::set_logic_op(const bool enable)
{
if (enable) {
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(GL_XOR);
}
else {
glDisable(GL_COLOR_LOGIC_OP);
}
}
void GLStateManager::set_facing(const bool invert)
{
glFrontFace((invert) ? GL_CW : GL_CCW);
}
void GLStateManager::set_backface_culling(const GPUFaceCullTest test)
{
if (test != GPU_CULL_NONE) {
glEnable(GL_CULL_FACE);
glCullFace((test == GPU_CULL_FRONT) ? GL_FRONT : GL_BACK);
}
else {
glDisable(GL_CULL_FACE);
}
}
void GLStateManager::set_provoking_vert(const GPUProvokingVertex vert)
{
GLenum value = (vert == GPU_VERTEX_FIRST) ? GL_FIRST_VERTEX_CONVENTION :
GL_LAST_VERTEX_CONVENTION;
glProvokingVertex(value);
}
void GLStateManager::set_clip_control(const bool enable)
{
if (enable) {
/* Match Vulkan and Metal by default. */
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
}
else {
glClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE);
}
}
void GLStateManager::set_blend(const GPUBlend value)
{
/**
* Factors to the equation.
* SRC is fragment shader output.
* DST is frame-buffer color.
* final.rgb = SRC.rgb * src_rgb + DST.rgb * dst_rgb;
* final.a = SRC.a * src_alpha + DST.a * dst_alpha;
*/
GLenum src_rgb, src_alpha, dst_rgb, dst_alpha;
switch (value) {
default:
case GPU_BLEND_ALPHA: {
src_rgb = GL_SRC_ALPHA;
dst_rgb = GL_ONE_MINUS_SRC_ALPHA;
src_alpha = GL_ONE;
dst_alpha = GL_ONE_MINUS_SRC_ALPHA;
break;
}
case GPU_BLEND_ALPHA_PREMULT: {
src_rgb = GL_ONE;
dst_rgb = GL_ONE_MINUS_SRC_ALPHA;
src_alpha = GL_ONE;
dst_alpha = GL_ONE_MINUS_SRC_ALPHA;
break;
}
case GPU_BLEND_ADDITIVE: {
/* Do not let alpha accumulate but pre-multiply the source RGB by it. */
src_rgb = GL_SRC_ALPHA;
dst_rgb = GL_ONE;
src_alpha = GL_ZERO;
dst_alpha = GL_ONE;
break;
}
/* Factors are not use in min or max mode, but avoid uninitialized values. */;
case GPU_BLEND_MIN:
case GPU_BLEND_MAX:
case GPU_BLEND_SUBTRACT:
case GPU_BLEND_ADDITIVE_PREMULT: {
/* Let alpha accumulate. */
src_rgb = GL_ONE;
dst_rgb = GL_ONE;
src_alpha = GL_ONE;
dst_alpha = GL_ONE;
break;
}
case GPU_BLEND_MULTIPLY: {
src_rgb = GL_DST_COLOR;
dst_rgb = GL_ZERO;
src_alpha = GL_DST_ALPHA;
dst_alpha = GL_ZERO;
break;
}
case GPU_BLEND_INVERT: {
src_rgb = GL_ONE_MINUS_DST_COLOR;
dst_rgb = GL_ZERO;
src_alpha = GL_ZERO;
dst_alpha = GL_ONE;
break;
}
case GPU_BLEND_OIT: {
src_rgb = GL_ONE;
dst_rgb = GL_ONE;
src_alpha = GL_ZERO;
dst_alpha = GL_ONE_MINUS_SRC_ALPHA;
break;
}
case GPU_BLEND_BACKGROUND: {
src_rgb = GL_ONE_MINUS_DST_ALPHA;
dst_rgb = GL_SRC_ALPHA;
src_alpha = GL_ZERO;
dst_alpha = GL_SRC_ALPHA;
break;
}
case GPU_BLEND_ALPHA_UNDER_PREMUL: {
src_rgb = GL_ONE_MINUS_DST_ALPHA;
dst_rgb = GL_ONE;
src_alpha = GL_ONE_MINUS_DST_ALPHA;
dst_alpha = GL_ONE;
break;
}
case GPU_BLEND_CUSTOM: {
src_rgb = GL_ONE;
dst_rgb = GL_SRC1_COLOR;
src_alpha = GL_ONE;
dst_alpha = GL_SRC1_ALPHA;
break;
}
case GPU_BLEND_OVERLAY_MASK_FROM_ALPHA: {
src_rgb = GL_ZERO;
dst_rgb = GL_ONE_MINUS_SRC_ALPHA;
src_alpha = GL_ZERO;
dst_alpha = GL_ONE_MINUS_SRC_ALPHA;
break;
}
case GPU_BLEND_TRANSPARENCY: {
src_rgb = GL_ONE;
dst_rgb = GL_SRC_ALPHA;
src_alpha = GL_ZERO;
dst_alpha = GL_SRC_ALPHA;
break;
}
}
if (value == GPU_BLEND_MIN) {
glBlendEquation(GL_MIN);
}
else if (value == GPU_BLEND_MAX) {
glBlendEquation(GL_MAX);
}
else if (value == GPU_BLEND_SUBTRACT) {
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
}
else {
glBlendEquation(GL_FUNC_ADD);
}
/* Always set the blend function. This avoid a rendering error when blending is disabled but
* GPU_BLEND_CUSTOM was used just before and the frame-buffer is using more than 1 color target.
*/
glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha);
if (value != GPU_BLEND_NONE) {
glEnable(GL_BLEND);
}
else {
glDisable(GL_BLEND);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Texture State Management
* \{ */
void GLStateManager::texture_bind(Texture *tex_, GPUSamplerState sampler_state, int unit)
{
BLI_assert(unit < GPU_max_textures());
GLTexture *tex = static_cast<GLTexture *>(tex_);
if (G.debug & G_DEBUG_GPU) {
tex->check_feedback_loop();
}
/* Eliminate redundant binds. */
if ((textures_[unit] == tex->tex_id_) &&
(samplers_[unit] == GLTexture::get_sampler(sampler_state)))
{
return;
}
targets_[unit] = tex->target_;
textures_[unit] = tex->tex_id_;
samplers_[unit] = GLTexture::get_sampler(sampler_state);
tex->is_bound_ = true;
dirty_texture_binds_ |= 1ULL << unit;
}
void GLStateManager::texture_bind_temp(GLTexture *tex)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex->target_, tex->tex_id_);
/* Will reset the first texture that was originally bound to slot 0 back before drawing. */
dirty_texture_binds_ |= 1ULL;
/* NOTE: This might leave this texture attached to this target even after update.
* In practice it is not causing problems as we have incorrect binding detection
* at higher level. */
}
void GLStateManager::texture_unbind(Texture *tex_)
{
GLTexture *tex = static_cast<GLTexture *>(tex_);
if (!tex->is_bound_) {
return;
}
GLuint tex_id = tex->tex_id_;
for (int i = 0; i < ARRAY_SIZE(textures_); i++) {
if (textures_[i] == tex_id) {
textures_[i] = 0;
samplers_[i] = 0;
dirty_texture_binds_ |= 1ULL << i;
}
}
tex->is_bound_ = false;
}
void GLStateManager::texture_unbind_all()
{
for (int i = 0; i < ARRAY_SIZE(textures_); i++) {
if (textures_[i] != 0) {
textures_[i] = 0;
samplers_[i] = 0;
dirty_texture_binds_ |= 1ULL << i;
}
}
this->texture_bind_apply();
}
void GLStateManager::texture_bind_apply()
{
if (dirty_texture_binds_ == 0) {
return;
}
uint64_t dirty_bind = dirty_texture_binds_;
dirty_texture_binds_ = 0;
int first = bitscan_forward_uint64(dirty_bind);
int last = 64 - bitscan_reverse_uint64(dirty_bind);
int count = last - first;
if (GLContext::multi_bind_support) {
glBindTextures(first, count, textures_ + first);
glBindSamplers(first, count, samplers_ + first);
}
else {
for (int unit = first; unit < last; unit++) {
if ((dirty_bind >> unit) & 1UL) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(targets_[unit], textures_[unit]);
glBindSampler(unit, samplers_[unit]);
}
}
}
}
uint64_t GLStateManager::bound_texture_slots()
{
uint64_t bound_slots = 0;
for (int i = 0; i < ARRAY_SIZE(textures_); i++) {
if (textures_[i] != 0) {
bound_slots |= 1ULL << i;
}
}
return bound_slots;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image Binding (from image load store)
* \{ */
void GLStateManager::image_bind(Texture *tex_, int unit)
{
/* Minimum support is 8 image in the fragment shader. No image for other stages. */
BLI_assert(unit < 8);
GLTexture *tex = static_cast<GLTexture *>(tex_);
if (G.debug & G_DEBUG_GPU) {
tex->check_feedback_loop();
}
images_[unit] = tex->tex_id_;
formats_[unit] = to_gl_internal_format(tex->format_);
image_formats[unit] = TextureWriteFormat(tex->format_get());
tex->is_bound_image_ = true;
dirty_image_binds_ |= 1ULL << unit;
}
void GLStateManager::image_unbind(Texture *tex_)
{
GLTexture *tex = static_cast<GLTexture *>(tex_);
if (!tex->is_bound_image_) {
return;
}
GLuint tex_id = tex->tex_id_;
for (int i = 0; i < ARRAY_SIZE(images_); i++) {
if (images_[i] == tex_id) {
images_[i] = 0;
image_formats[i] = TextureWriteFormat::Invalid;
dirty_image_binds_ |= 1ULL << i;
}
}
tex->is_bound_image_ = false;
}
void GLStateManager::image_unbind_all()
{
for (int i = 0; i < ARRAY_SIZE(images_); i++) {
if (images_[i] != 0) {
images_[i] = 0;
dirty_image_binds_ |= 1ULL << i;
}
}
image_formats.fill(TextureWriteFormat::Invalid);
this->image_bind_apply();
}
void GLStateManager::image_bind_apply()
{
if (dirty_image_binds_ == 0) {
return;
}
uint32_t dirty_bind = dirty_image_binds_;
dirty_image_binds_ = 0;
int first = bitscan_forward_uint(dirty_bind);
int last = 32 - bitscan_reverse_uint(dirty_bind);
int count = last - first;
if (GLContext::multi_bind_image_support) {
glBindImageTextures(first, count, images_ + first);
}
else {
for (int unit = first; unit < last; unit++) {
if ((dirty_bind >> unit) & 1UL) {
glBindImageTexture(unit, images_[unit], 0, GL_TRUE, 0, GL_READ_WRITE, formats_[unit]);
}
}
}
}
uint8_t GLStateManager::bound_image_slots()
{
uint8_t bound_slots = 0;
for (int i = 0; i < ARRAY_SIZE(images_); i++) {
if (images_[i] != 0) {
bound_slots |= 1ULL << i;
}
}
return bound_slots;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Memory barrier
* \{ */
void GLStateManager::issue_barrier(GPUBarrier barrier_bits)
{
glMemoryBarrier(to_gl(barrier_bits));
}
GLFence::~GLFence()
{
if (gl_sync_ != nullptr) {
glDeleteSync(gl_sync_);
gl_sync_ = nullptr;
}
}
void GLFence::signal()
{
/* If fence is already signaled, create a newly signaled 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_ == nullptr) {
return;
}
glWaitSync(gl_sync_, 0, GL_TIMEOUT_IGNORED);
signalled_ = false;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,156 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "gpu_state_private.hh"
#include <epoxy/gl.h>
namespace blender::gpu {
class GLFrameBuffer;
class GLTexture;
/**
* State manager keeping track of the draw state and applying it before drawing.
* Opengl Implementation.
*/
class GLStateManager : public StateManager {
public:
/** Another reference to the active frame-buffer. */
GLFrameBuffer *active_fb = nullptr;
private:
/** Current state of the GL implementation. Avoids resetting the whole state for every change. */
GPUState current_;
GPUStateMutable current_mutable_;
/** Limits. */
float line_width_range_[2];
/**
* Texture state:
* We keep the full stack of textures and sampler bounds to use multi bind, and to be able to
* edit and restore texture binds on the fly without querying the context.
* Also this allows us to keep track of textures bounds to many texture units.
* Keep the targets to know what target to set to 0 for unbinding (legacy).
* Init first target to GL_TEXTURE_2D for texture_bind_temp to work.
*/
GLuint targets_[64] = {GL_TEXTURE_2D};
GLuint textures_[64] = {0};
GLuint samplers_[64] = {0};
uint64_t dirty_texture_binds_ = 0;
GLuint images_[8] = {0};
GLenum formats_[8] = {0};
uint8_t dirty_image_binds_ = 0;
public:
GLStateManager();
void apply_state() override;
/**
* Will set all the states regardless of the current ones.
*/
void force_state() override;
void issue_barrier(GPUBarrier barrier_bits) override;
void texture_bind(Texture *tex, GPUSamplerState sampler, int unit) override;
/**
* Bind the texture to slot 0 for editing purpose. Used by legacy pipeline.
*/
void texture_bind_temp(GLTexture *tex);
void texture_unbind(Texture *tex) override;
void texture_unbind_all() override;
void image_bind(Texture *tex, int unit) override;
void image_unbind(Texture *tex) override;
void image_unbind_all() override;
uint64_t bound_texture_slots();
uint8_t bound_image_slots();
private:
static void set_write_mask(GPUWriteMask value);
static void set_depth_test(GPUDepthTest value);
static void set_stencil_test(GPUStencilTest test, GPUStencilOp operation);
static void set_stencil_mask(GPUStencilTest test, const GPUStateMutable &state);
static void set_clip_distances(int new_dist_len, int old_dist_len);
static void set_logic_op(bool enable);
static void set_facing(bool invert);
static void set_backface_culling(GPUFaceCullTest test);
static void set_provoking_vert(GPUProvokingVertex vert);
static void set_clip_control(bool enable);
static void set_blend(GPUBlend value);
void set_state(const GPUState &state);
void set_mutable_state(const GPUStateMutable &state);
void texture_bind_apply();
void image_bind_apply();
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(GPUBarrier barrier_bits)
{
GLbitfield barrier = 0;
if (barrier_bits & GPU_BARRIER_SHADER_IMAGE_ACCESS) {
barrier |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_SHADER_STORAGE) {
barrier |= GL_SHADER_STORAGE_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_TEXTURE_FETCH) {
barrier |= GL_TEXTURE_FETCH_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_TEXTURE_UPDATE) {
barrier |= GL_TEXTURE_UPDATE_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_COMMAND) {
barrier |= GL_COMMAND_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_FRAMEBUFFER) {
barrier |= GL_FRAMEBUFFER_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_VERTEX_ATTRIB_ARRAY) {
barrier |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_ELEMENT_ARRAY) {
barrier |= GL_ELEMENT_ARRAY_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_UNIFORM) {
barrier |= GL_UNIFORM_BARRIER_BIT;
}
if (barrier_bits & GPU_BARRIER_BUFFER_UPDATE) {
barrier |= GL_BUFFER_UPDATE_BARRIER_BIT;
}
return barrier;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,262 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_string.h"
#include "GPU_capabilities.hh"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gl_backend.hh"
#include "gl_debug.hh"
#include "gl_storage_buffer.hh"
#include "gl_vertex_buffer.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
GLStorageBuf::GLStorageBuf(size_t size, GPUUsageType usage, const char *name)
: StorageBuf(size, name)
{
usage_ = usage;
/* Do not create SSBO GL buffer here to allow allocation from any thread. */
BLI_assert(size <= GPU_max_storage_buffer_size());
}
GLStorageBuf::~GLStorageBuf()
{
if (read_fence_) {
glDeleteSync(read_fence_);
}
if (persistent_ptr_) {
if (GLContext::direct_state_access_support) {
glUnmapNamedBuffer(read_ssbo_id_);
}
else {
glBindBuffer(GL_SHADER_STORAGE_BUFFER, read_ssbo_id_);
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
}
if (read_ssbo_id_) {
GLContext::buffer_free(read_ssbo_id_);
}
GLContext::buffer_free(ssbo_id_);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Data upload / update
* \{ */
void GLStorageBuf::init()
{
BLI_assert(GLContext::get());
alloc_size_in_bytes_ = ceil_to_multiple_ul(size_in_bytes_, 16);
glGenBuffers(1, &ssbo_id_);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo_id_);
glBufferData(GL_SHADER_STORAGE_BUFFER, alloc_size_in_bytes_, nullptr, to_gl(this->usage_));
debug::object_label(GL_SHADER_STORAGE_BUFFER, ssbo_id_, name_);
}
void GLStorageBuf::update(const void *data)
{
if (ssbo_id_ == 0) {
this->init();
}
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo_id_);
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, size_in_bytes_, data);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Usage
* \{ */
void GLStorageBuf::bind(int slot)
{
if (slot >= GLContext::max_ssbo_binds) {
fprintf(
stderr,
"Error: Trying to bind \"%s\" ssbo to slot %d which is above the reported limit of %d.\n",
name_,
slot,
GLContext::max_ssbo_binds);
return;
}
if (ssbo_id_ == 0) {
this->init();
}
if (data_ != nullptr) {
this->update(data_);
MEM_SAFE_DELETE_VOID(data_);
}
slot_ = slot;
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot_, ssbo_id_);
#ifndef NDEBUG
BLI_assert(slot < 16);
GLContext::get()->bound_ssbo_slots |= 1 << slot;
#endif
}
void GLStorageBuf::bind_as(GLenum target)
{
BLI_assert_msg(ssbo_id_ != 0,
"Trying to use storage buffer as indirect buffer but buffer was never filled.");
glBindBuffer(target, ssbo_id_);
}
void GLStorageBuf::unbind()
{
#ifndef NDEBUG
/* NOTE: This only unbinds the last bound slot. */
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot_, 0);
/* Hope that the context did not change. */
GLContext::get()->bound_ssbo_slots &= ~(1 << slot_);
#endif
slot_ = 0;
}
void GLStorageBuf::clear(uint32_t clear_value)
{
if (ssbo_id_ == 0) {
this->init();
}
if (GLContext::direct_state_access_support) {
glClearNamedBufferData(ssbo_id_, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, &clear_value);
}
else {
/* WATCH(@fclem): This should be ok since we only use clear outside of drawing functions. */
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo_id_);
glClearBufferData(
GL_SHADER_STORAGE_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, &clear_value);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
}
void GLStorageBuf::copy_sub(VertBuf *src_, uint dst_offset, uint src_offset, uint copy_size)
{
GLVertBuf *src = static_cast<GLVertBuf *>(src_);
GLStorageBuf *dst = this;
if (dst->ssbo_id_ == 0) {
dst->init();
}
if (src->vbo_id_ == 0) {
src->bind();
}
if (GLContext::direct_state_access_support) {
glCopyNamedBufferSubData(src->vbo_id_, dst->ssbo_id_, src_offset, dst_offset, copy_size);
}
else {
/* This binds the buffer to GL_ARRAY_BUFFER and upload the data if any. */
src->bind();
glBindBuffer(GL_COPY_WRITE_BUFFER, dst->ssbo_id_);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, src_offset, dst_offset, copy_size);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
}
}
void GLStorageBuf::async_flush_to_host()
{
if (ssbo_id_ == 0) {
this->init();
}
if (read_ssbo_id_ == 0) {
glGenBuffers(1, &read_ssbo_id_);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, read_ssbo_id_);
glBufferStorage(GL_SHADER_STORAGE_BUFFER,
alloc_size_in_bytes_,
nullptr,
GL_MAP_PERSISTENT_BIT | GL_MAP_READ_BIT);
persistent_ptr_ = glMapBufferRange(GL_SHADER_STORAGE_BUFFER,
0,
alloc_size_in_bytes_,
GL_MAP_PERSISTENT_BIT | GL_MAP_READ_BIT);
BLI_assert(persistent_ptr_);
debug::object_label(GL_SHADER_STORAGE_BUFFER, read_ssbo_id_, name_);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
if (GLContext::direct_state_access_support) {
glCopyNamedBufferSubData(ssbo_id_, read_ssbo_id_, 0, 0, alloc_size_in_bytes_);
}
else {
glBindBuffer(GL_COPY_READ_BUFFER, ssbo_id_);
glBindBuffer(GL_COPY_WRITE_BUFFER, read_ssbo_id_);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, alloc_size_in_bytes_);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
}
glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
if (read_fence_) {
glDeleteSync(read_fence_);
}
read_fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
void GLStorageBuf::read(void *data)
{
if (data == nullptr) {
return;
}
if (!read_fence_) {
/* Synchronous path. */
if (GLContext::direct_state_access_support) {
glGetNamedBufferSubData(ssbo_id_, 0, size_in_bytes_, data);
}
else {
glBindBuffer(GL_COPY_READ_BUFFER, ssbo_id_);
glGetBufferSubData(GL_COPY_READ_BUFFER, 0, size_in_bytes_, data);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
}
return;
}
while (glClientWaitSync(read_fence_, GL_SYNC_FLUSH_COMMANDS_BIT, 1000) == GL_TIMEOUT_EXPIRED) {
/* Repeat until the data is ready. */
}
glDeleteSync(read_fence_);
read_fence_ = nullptr;
BLI_assert(persistent_ptr_);
memcpy(data, persistent_ptr_, size_in_bytes_);
}
void GLStorageBuf::sync_as_indirect_buffer()
{
bind_as(GL_DRAW_INDIRECT_BUFFER);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "GPU_vertex_buffer.hh"
#include "gpu_storage_buffer_private.hh"
namespace blender::gpu {
/**
* Implementation of Storage Buffers using OpenGL.
*/
class GLStorageBuf : public StorageBuf {
private:
/** Slot to which this UBO is currently bound. -1 if not bound. */
int slot_ = -1;
/** OpenGL Object handle. */
GLuint ssbo_id_ = 0;
/** Usage type. */
GPUUsageType usage_ = GPUUsageType(-1);
/* Read */
GLuint read_ssbo_id_ = 0;
GLsync read_fence_ = 0;
void *persistent_ptr_ = nullptr;
size_t alloc_size_in_bytes_ = 0;
public:
GLStorageBuf(size_t size, GPUUsageType usage, const char *name);
~GLStorageBuf();
void update(const void *data) override;
void bind(int slot) override;
void unbind() override;
void clear(uint32_t clear_value) override;
void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override;
void read(void *data) override;
void async_flush_to_host() override;
void sync_as_indirect_buffer() override;
/* Special internal function to bind SSBOs to indirect argument targets. */
void bind_as(GLenum target);
private:
void init();
MEM_CXX_CLASS_ALLOC_FUNCS("GLStorageBuf");
};
} // namespace blender::gpu

View File

@@ -0,0 +1,905 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <cstdint>
#include <string>
#include "BLI_assert.h"
#include "BLI_math_half.hh"
#include "BLI_string.h"
#include "DNA_userdef_types.h"
#include "GPU_capabilities.hh"
#include "GPU_framebuffer.hh"
#include "GPU_platform.hh"
#include "GPU_vertex_buffer.hh" /* TODO: should be `gl_vertex_buffer.hh`. */
#include "MEM_guardedalloc.h"
#include "gl_backend.hh"
#include "gl_debug.hh"
#include "gl_state.hh"
#include "gl_texture.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
GLTexture::GLTexture(const char *name) : Texture(name)
{
BLI_assert(GLContext::get() != nullptr);
glGenTextures(1, &tex_id_);
}
GLTexture::~GLTexture()
{
if (framebuffer_) {
GPU_framebuffer_free(framebuffer_);
}
GLContext *ctx = GLContext::get();
if (ctx != nullptr && is_bound_) {
/* This avoid errors when the texture is still inside the bound texture array. */
ctx->state_manager->texture_unbind(this);
ctx->state_manager->image_unbind(this);
}
GLContext::texture_free(tex_id_);
}
bool GLTexture::init_internal()
{
target_ = to_gl_target(type_);
/* We need to bind once to define the texture type. */
GLContext::state_manager_active_get()->texture_bind_temp(this);
if (!this->proxy_check(0)) {
return false;
}
GLenum internal_format = to_gl_internal_format(format_);
const bool is_cubemap = bool(type_ == GPU_TEXTURE_CUBE);
const int dimensions = (is_cubemap) ? 2 : this->dimensions_count();
switch (dimensions) {
default:
case 1:
glTexStorage1D(target_, mipmaps_, internal_format, w_);
break;
case 2:
glTexStorage2D(target_, mipmaps_, internal_format, w_, h_);
break;
case 3:
glTexStorage3D(target_, mipmaps_, internal_format, w_, h_, d_);
break;
}
this->mip_range_set(0, mipmaps_ - 1);
/* Avoid issue with formats not supporting filtering. Nearest by default. */
if (GLContext::direct_state_access_support) {
glTextureParameteri(tex_id_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
else {
glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
debug::object_label(GL_TEXTURE, tex_id_, name_.c_str());
return true;
}
bool GLTexture::init_internal(VertBuf *vbo)
{
GLVertBuf *gl_vbo = static_cast<GLVertBuf *>(vbo);
target_ = to_gl_target(type_);
/* We need to bind once to define the texture type. */
GLContext::state_manager_active_get()->texture_bind_temp(this);
GLenum internal_format = to_gl_internal_format(format_);
if (GLContext::direct_state_access_support) {
glTextureBuffer(tex_id_, internal_format, gl_vbo->vbo_id_);
}
else {
glTexBuffer(target_, internal_format, gl_vbo->vbo_id_);
}
debug::object_label(GL_TEXTURE, tex_id_, name_.c_str());
return true;
}
bool GLTexture::init_internal(gpu::Texture *src,
int mip_offset,
int layer_offset,
bool use_stencil)
{
const GLTexture *gl_src = static_cast<const GLTexture *>(src);
GLenum internal_format = to_gl_internal_format(format_);
target_ = to_gl_target(type_);
glTextureView(tex_id_,
target_,
gl_src->tex_id_,
internal_format,
mip_offset,
mipmaps_,
layer_offset,
this->layer_count());
debug::object_label(GL_TEXTURE, tex_id_, name_.c_str());
/* Stencil view support. */
if (ELEM(format_, TextureFormat::SFLOAT_32_DEPTH_UINT_8)) {
stencil_texture_mode_set(use_stencil);
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operations
* \{ */
void GLTexture::update_sub_direct_state_access(
int mip, int offset[3], int extent[3], GLenum format, GLenum type, const void *data)
{
if (format_flag_ & GPU_FORMAT_COMPRESSED) {
size_t size = ((extent[0] + 3) / 4) * ((extent[1] + 3) / 4) * to_block_size(format_);
switch (this->dimensions_count()) {
default:
case 1:
glCompressedTextureSubImage1D(tex_id_, mip, offset[0], extent[0], format, size, data);
break;
case 2:
glCompressedTextureSubImage2D(
tex_id_, mip, UNPACK2(offset), UNPACK2(extent), format, size, data);
break;
case 3:
glCompressedTextureSubImage3D(
tex_id_, mip, UNPACK3(offset), UNPACK3(extent), format, size, data);
break;
}
}
else {
switch (this->dimensions_count()) {
default:
case 1:
glTextureSubImage1D(tex_id_, mip, offset[0], extent[0], format, type, data);
break;
case 2:
glTextureSubImage2D(tex_id_, mip, UNPACK2(offset), UNPACK2(extent), format, type, data);
break;
case 3:
glTextureSubImage3D(tex_id_, mip, UNPACK3(offset), UNPACK3(extent), format, type, data);
break;
}
}
has_pixels_ = true;
}
void GLTexture::update_sub(int mip,
int offset[3],
int extent[3],
eGPUDataFormat type,
const void *data,
const uint unpack_row_length)
{
BLI_assert(validate_data_format(format_, type));
BLI_assert(data != nullptr);
if (mip >= mipmaps_) {
debug::raise_gl_error("Updating a miplvl on a texture too small to have this many levels.");
return;
}
/* If `unpack_row_length` is 0, rows are sequentially stored. Otherwise we unpack data
* into a staging block, so the half conversion below doesn't happen on the full input. */
const bool do_texture_unpack = !ELEM(unpack_row_length, 0, extent[0]);
/* Unpack `data` if `unpack_row_length` is set. */
std::unique_ptr<uint8_t, MEM_smart_ptr_deleter<uint8_t>> unpack_buffer = nullptr;
if (do_texture_unpack) {
BLI_assert_msg(!(format_flag_ & GPU_FORMAT_COMPRESSED),
"Compressed data with unpack_row_length != 0 is not supported.");
BLI_assert_msg(extent[2] <= 1,
"3D texture data with unpack_row_length != 0 is not supported.");
size_t src_row_stride = unpack_row_length * to_bytesize(format_, type);
size_t dst_row_stride = max_ii(extent[0], 1) * to_bytesize(format_, type);
size_t dst_total_count = dst_row_stride * max_ii(extent[1], 1) * max_ii(extent[2], 1);
/* Allocate buffer to size necessary for gather */
unpack_buffer.reset(
static_cast<uint8_t *>(MEM_new_uninitialized_aligned(dst_total_count, 128, __func__)));
/* Strided loop; we advance source and destination pointers separately during a gather. */
const uint8_t *src_ptr = static_cast<const uint8_t *>(data);
uint8_t *dst_ptr = unpack_buffer.get();
for (int y = 0; y < max_ii(extent[1], 1); ++y) {
std::memcpy(dst_ptr, src_ptr, dst_row_stride);
src_ptr += src_row_stride;
dst_ptr += dst_row_stride;
}
/* Replace the 'data' ptr with `unpack_buffer`,
* which has lifetime in the function scope. */
data = unpack_buffer.get();
}
GLenum gl_type = to_gl(type);
/* If `data` is float and target storage is half, convert to half */
std::unique_ptr<uint16_t, MEM_smart_ptr_deleter<uint16_t>> clamped_half_buffer = nullptr;
if (type == GPU_DATA_FLOAT && is_half_float(format_)) {
size_t dst_pixel_count = max_ii(extent[0], 1) * max_ii(extent[1], 1) * max_ii(extent[2], 1);
size_t dst_total_count = to_component_len(format_) * dst_pixel_count;
/* Allocate buffer to size necessary for conversion.. */
clamped_half_buffer.reset(static_cast<uint16_t *>(
MEM_new_uninitialized_aligned(sizeof(uint16_t) * dst_total_count, 128, __func__)));
Span<float> src(static_cast<const float *>(data), dst_total_count);
MutableSpan<uint16_t> dst(static_cast<uint16_t *>(clamped_half_buffer.get()), dst_total_count);
constexpr int64_t chunk_size = 4 * 1024 * 1024;
threading::parallel_for(IndexRange(dst_total_count), chunk_size, [&](const IndexRange range) {
/* Doing float to half conversion manually to avoid implementation specific behavior
* regarding Inf and NaNs. Use make finite version to avoid unexpected black pixels on
* certain implementation. For platform parity we clamp these infinite values to finite
* values. */
math::float_to_half_make_finite_array(
src.slice(range).data(), dst.slice(range).data(), range.size());
});
/* Replace the 'data' ptr with `clamped_half_buffer`,
* which has lifetime in the function scope. */
data = clamped_half_buffer.get();
gl_type = to_gl(GPU_DATA_HALF_FLOAT);
/* If the `data` ptr was previously replaced by `unpack_buffer`,
* clear `unpack_buffer` as it is no longer necessary. */
if (do_texture_unpack) {
unpack_buffer.reset(nullptr);
}
}
/* TextureFormat::SINT_16_16 formats with integer data does not seem to work correctly in all GPU
* drivers, so convert to short and use GL_SHORT. */
std::unique_ptr<int16_t, MEM_smart_ptr_deleter<int16_t>> short_buffer = nullptr;
if (type == GPU_DATA_INT && format_ == TextureFormat::SINT_16_16) {
size_t dst_pixel_count = max_ii(extent[0], 1) * max_ii(extent[1], 1) * max_ii(extent[2], 1);
size_t dst_total_count = to_component_len(format_) * dst_pixel_count;
short_buffer.reset(static_cast<int16_t *>(
MEM_new_uninitialized_aligned(sizeof(int16_t) * dst_total_count, 128, __func__)));
Span<int32_t> src(static_cast<const int32_t *>(data), dst_total_count);
MutableSpan<int16_t> dst(static_cast<int16_t *>(short_buffer.get()), dst_total_count);
constexpr int64_t chunk_size = 4 * 1024 * 1024;
threading::parallel_for(IndexRange(dst_total_count), chunk_size, [&](const IndexRange range) {
for (const int64_t i : range) {
dst[i] = int16_t(src[i]);
}
});
/* Replace the 'data' ptr with `short_buffer`, which has lifetime in the function scope. */
data = short_buffer.get();
gl_type = GL_SHORT;
/* If the `data` ptr was previously replaced by `unpack_buffer`,
* clear `unpack_buffer` as it is no longer necessary. */
if (do_texture_unpack) {
unpack_buffer.reset(nullptr);
}
}
const int dimensions = this->dimensions_count();
GLenum gl_format = to_gl_data_format(format_);
/* Some drivers have issues with cubemap & glTextureSubImage3D even if it is correct. */
if (GLContext::direct_state_access_support && (type_ != GPU_TEXTURE_CUBE)) {
this->update_sub_direct_state_access(mip, offset, extent, gl_format, gl_type, data);
return;
}
GLContext::state_manager_active_get()->texture_bind_temp(this);
if (type_ == GPU_TEXTURE_CUBE) {
for (int i = 0; i < extent[2]; i++) {
GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + offset[2] + i;
glTexSubImage2D(target, mip, UNPACK2(offset), UNPACK2(extent), gl_format, gl_type, data);
}
}
else if (format_flag_ & GPU_FORMAT_COMPRESSED) {
size_t size = ((extent[0] + 3) / 4) * ((extent[1] + 3) / 4) * to_block_size(format_);
switch (dimensions) {
default:
case 1:
glCompressedTexSubImage1D(target_, mip, offset[0], extent[0], gl_format, size, data);
break;
case 2:
glCompressedTexSubImage2D(
target_, mip, UNPACK2(offset), UNPACK2(extent), gl_format, size, data);
break;
case 3:
glCompressedTexSubImage3D(
target_, mip, UNPACK3(offset), UNPACK3(extent), gl_format, size, data);
break;
}
}
else {
switch (dimensions) {
default:
case 1:
glTexSubImage1D(target_, mip, offset[0], extent[0], gl_format, gl_type, data);
break;
case 2:
glTexSubImage2D(target_, mip, UNPACK2(offset), UNPACK2(extent), gl_format, gl_type, data);
break;
case 3:
glTexSubImage3D(target_, mip, UNPACK3(offset), UNPACK3(extent), gl_format, gl_type, data);
break;
}
}
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. */
GLint pix_buf_handle = GLint(GPU_pixel_buffer_get_native_handle(pixbuf).handle);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pix_buf_handle);
switch (dimensions) {
default:
case 1:
glTexSubImage1D(target_, 0, offset[0], extent[0], gl_format, gl_type, nullptr);
break;
case 2:
glTexSubImage2D(target_, 0, UNPACK2(offset), UNPACK2(extent), gl_format, gl_type, nullptr);
break;
case 3:
glTexSubImage3D(target_, 0, UNPACK3(offset), UNPACK3(extent), gl_format, gl_type, nullptr);
break;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
void GLTexture::generate_mipmap()
{
/* Allow users to provide mipmaps stored in compressed textures.
* Skip generating mipmaps to avoid overriding the existing ones. */
if (format_flag_ & GPU_FORMAT_COMPRESSED) {
return;
}
/* Some drivers have bugs when using #glGenerateMipmap with depth textures (see #56789).
* In this case we just create a complete texture with mipmaps manually without
* down-sampling. You must initialize the texture levels using other methods. */
if (format_flag_ & GPU_FORMAT_DEPTH) {
return;
}
if (GLContext::generate_mipmap_workaround) {
/* Broken glGenerateMipmap, don't call it and render without mipmaps.
* If no top level pixels have been filled in, the levels will get filled by
* other means and there is no need to disable mipmapping. */
if (has_pixels_) {
this->mip_range_set(0, 0);
}
return;
}
/* Down-sample from mip 0 using implementation. */
if (GLContext::direct_state_access_support) {
glGenerateTextureMipmap(tex_id_);
}
else {
GLContext::state_manager_active_get()->texture_bind_temp(this);
glGenerateMipmap(target_);
}
}
void GLTexture::clear(const double4 data)
{
/* Note: do not use glClearTexImage, even if it is available (via
* extension or GL 4.4). It causes GL framebuffer binding to be
* way slower at least on some drivers (e.g. Win10 / NV RTX 3080,
* but also reportedly others), as if glClearTexImage causes
* "pixel data" to exist which is then uploaded CPU -> GPU at bind
* time. */
gpu::FrameBuffer *prev_fb = GPU_framebuffer_active_get();
FrameBuffer *fb = this->framebuffer_get();
fb->bind(true);
fb->clear_attachment(this->attachment_type(0), data);
GPU_framebuffer_bind(prev_fb);
}
void GLTexture::copy_to(Texture *dst_, IndexRange mip_levels)
{
GLTexture *dst = static_cast<GLTexture *>(dst_);
GLTexture *src = this;
BLI_assert((dst->w_ == src->w_) && (dst->h_ == src->h_) && (dst->d_ == src->d_));
BLI_assert((src->format_ == dst->format_) ||
(src->format_ == TextureFormat::SRGBA_8_8_8_8 &&
dst->format_ == TextureFormat::UNORM_8_8_8_8) ||
(src->format_ == TextureFormat::UNORM_8_8_8_8 &&
dst->format_ == TextureFormat::SRGBA_8_8_8_8));
BLI_assert(dst->type_ == src->type_);
for (int mip : mip_levels) {
/* NOTE: mip_size_get() won't override any dimension that is equal to 0. */
int extent[3] = {1, 1, 1};
this->mip_size_get(mip, extent);
glCopyImageSubData(
src->tex_id_, target_, mip, 0, 0, 0, dst->tex_id_, target_, mip, 0, 0, 0, UNPACK3(extent));
}
has_pixels_ = true;
}
void GLTexture::read(int mip, eGPUDataFormat type, void *data)
{
BLI_assert(!(format_flag_ & GPU_FORMAT_COMPRESSED));
BLI_assert(mip <= mipmaps_ || mip == 0);
BLI_assert(validate_data_format(format_, type));
size_t texture_size = read_size_get(mip, type);
GLenum gl_format = to_gl_data_format(
format_ == TextureFormat::SFLOAT_32_DEPTH_UINT_8 ? TextureFormat::SFLOAT_32_DEPTH : format_);
GLenum gl_type = to_gl(type);
if (GLContext::direct_state_access_support) {
glGetTextureImage(tex_id_, mip, gl_format, gl_type, texture_size, data);
}
else {
GLContext::state_manager_active_get()->texture_bind_temp(this);
if (type_ == GPU_TEXTURE_CUBE) {
size_t cube_face_size = texture_size / 6;
char *pdata = static_cast<char *>(data);
for (int i = 0; i < 6; i++, pdata += cube_face_size) {
glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mip, gl_format, gl_type, pdata);
}
}
else {
glGetTexImage(target_, mip, gl_format, gl_type, data);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Getters & setters
* \{ */
void GLTexture::swizzle_set(const char swizzle[4])
{
GLint gl_swizzle[4] = {GLint(swizzle_to_gl(swizzle[0])),
GLint(swizzle_to_gl(swizzle[1])),
GLint(swizzle_to_gl(swizzle[2])),
GLint(swizzle_to_gl(swizzle[3]))};
if (GLContext::direct_state_access_support) {
glTextureParameteriv(tex_id_, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle);
}
else {
GLContext::state_manager_active_get()->texture_bind_temp(this);
glTexParameteriv(target_, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle);
}
}
void GLTexture::stencil_texture_mode_set(bool use_stencil)
{
BLI_assert(GLContext::stencil_texturing_support);
GLint value = use_stencil ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
if (GLContext::direct_state_access_support) {
glTextureParameteri(tex_id_, GL_DEPTH_STENCIL_TEXTURE_MODE, value);
}
else {
GLContext::state_manager_active_get()->texture_bind_temp(this);
glTexParameteri(target_, GL_DEPTH_STENCIL_TEXTURE_MODE, value);
}
}
void GLTexture::mip_range_set(int min, int max)
{
BLI_assert(min <= max && min >= 0 && max <= mipmaps_);
mip_min_ = min;
mip_max_ = max;
if (GLContext::direct_state_access_support) {
glTextureParameteri(tex_id_, GL_TEXTURE_BASE_LEVEL, min);
glTextureParameteri(tex_id_, GL_TEXTURE_MAX_LEVEL, max);
}
else {
GLContext::state_manager_active_get()->texture_bind_temp(this);
glTexParameteri(target_, GL_TEXTURE_BASE_LEVEL, min);
glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, max);
}
}
FrameBuffer *GLTexture::framebuffer_get()
{
if (framebuffer_) {
GLFrameBuffer *gl_framebuffer = static_cast<GLFrameBuffer *>(framebuffer_);
if (gl_framebuffer->context_get() == GLContext::get()) {
return framebuffer_;
}
/* Textures can be shared between contexts but this helper framebuffer cannot. */
GPU_framebuffer_free(framebuffer_);
framebuffer_ = nullptr;
}
BLI_assert(!(type_ & GPU_TEXTURE_1D));
framebuffer_ = GPU_framebuffer_create(name_.c_str());
framebuffer_->attachment_set(this->attachment_type(0), GPU_ATTACHMENT_TEXTURE(this));
has_pixels_ = true;
return framebuffer_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sampler objects
* \{ */
/** A function that maps GPUSamplerExtendMode values to their OpenGL enum counterparts. */
static inline GLenum to_gl(GPUSamplerExtendMode extend_mode)
{
switch (extend_mode) {
case GPU_SAMPLER_EXTEND_MODE_EXTEND:
return GL_CLAMP_TO_EDGE;
case GPU_SAMPLER_EXTEND_MODE_REPEAT:
return GL_REPEAT;
case GPU_SAMPLER_EXTEND_MODE_MIRRORED_REPEAT:
return GL_MIRRORED_REPEAT;
case GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER:
return GL_CLAMP_TO_BORDER;
default:
BLI_assert_unreachable();
return GL_CLAMP_TO_EDGE;
}
}
GLuint GLTexture::samplers_state_cache_[GPU_SAMPLER_EXTEND_MODES_COUNT]
[GPU_SAMPLER_EXTEND_MODES_COUNT]
[GPU_SAMPLER_FILTERING_TYPES_COUNT] = {};
GLuint GLTexture::custom_samplers_state_cache_[GPU_SAMPLER_CUSTOM_TYPES_COUNT] = {};
void GLTexture::samplers_init()
{
glGenSamplers(samplers_state_cache_count_, &samplers_state_cache_[0][0][0]);
float max_anisotropy = 1.0f;
if (GLContext::texture_filter_anisotropic_support) {
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy);
}
for (int extend_yz_i = 0; extend_yz_i < GPU_SAMPLER_EXTEND_MODES_COUNT; extend_yz_i++) {
const GPUSamplerExtendMode extend_yz = static_cast<GPUSamplerExtendMode>(extend_yz_i);
const GLenum extend_t = to_gl(extend_yz);
for (int extend_x_i = 0; extend_x_i < GPU_SAMPLER_EXTEND_MODES_COUNT; extend_x_i++) {
const GPUSamplerExtendMode extend_x = static_cast<GPUSamplerExtendMode>(extend_x_i);
const GLenum extend_s = to_gl(extend_x);
for (int filtering_i = 0; filtering_i < GPU_SAMPLER_FILTERING_TYPES_COUNT; filtering_i++) {
const GPUSamplerFiltering filtering = GPUSamplerFiltering(filtering_i);
const GLenum mag_filter = (filtering & GPU_SAMPLER_FILTERING_LINEAR) ? GL_LINEAR :
GL_NEAREST;
const GLenum linear_min_filter = (filtering & GPU_SAMPLER_FILTERING_MIPMAP) ?
GL_LINEAR_MIPMAP_LINEAR :
GL_LINEAR;
const GLenum nearest_min_filter = (filtering & GPU_SAMPLER_FILTERING_MIPMAP) ?
GL_NEAREST_MIPMAP_LINEAR :
GL_NEAREST;
const GLenum min_filter = (filtering & GPU_SAMPLER_FILTERING_LINEAR) ? linear_min_filter :
nearest_min_filter;
GLuint sampler = samplers_state_cache_[extend_yz_i][extend_x_i][filtering_i];
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, extend_s);
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, extend_t);
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, extend_t);
glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, min_filter);
glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, mag_filter);
if (GLContext::texture_filter_anisotropic_support &&
(filtering & GPU_SAMPLER_FILTERING_MIPMAP) &&
(filtering & GPU_SAMPLER_FILTERING_ANISOTROPIC_MASK))
{
glSamplerParameterf(
sampler,
GL_TEXTURE_MAX_ANISOTROPY_EXT,
min_ff(float(GPU_anisotropic_samples_get(filtering)), max_anisotropy));
}
/* Other states are left to default:
* - GL_TEXTURE_BORDER_COLOR is {0, 0, 0, 0}.
* - GL_TEXTURE_MIN_LOD is -1000.
* - GL_TEXTURE_MAX_LOD is 1000.
* - GL_TEXTURE_LOD_BIAS is 0.0f.
*/
const GPUSamplerState sampler_state = {filtering, extend_x, extend_yz};
const std::string sampler_name = sampler_state.to_string();
debug::object_label(GL_SAMPLER, sampler, sampler_name.c_str());
}
}
}
glGenSamplers(GPU_SAMPLER_CUSTOM_TYPES_COUNT, custom_samplers_state_cache_);
/* Compare sampler for depth textures. */
GLuint compare_sampler = custom_samplers_state_cache_[GPU_SAMPLER_CUSTOM_COMPARE];
glSamplerParameteri(compare_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(compare_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(compare_sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(compare_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glSamplerParameteri(compare_sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glSamplerParameteri(compare_sampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glSamplerParameteri(compare_sampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
debug::object_label(GL_SAMPLER, compare_sampler, "compare");
/* Custom sampler for icons. The icon texture is sampled within the shader using a -0.5f LOD
* bias. */
GLuint icon_sampler = custom_samplers_state_cache_[GPU_SAMPLER_CUSTOM_ICON];
glSamplerParameteri(icon_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glSamplerParameteri(icon_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
debug::object_label(GL_SAMPLER, icon_sampler, "icons");
}
void GLTexture::samplers_free()
{
glDeleteSamplers(samplers_state_cache_count_, &samplers_state_cache_[0][0][0]);
glDeleteSamplers(GPU_SAMPLER_CUSTOM_TYPES_COUNT, custom_samplers_state_cache_);
}
GLuint GLTexture::get_sampler(const GPUSamplerState &sampler_state)
{
/* Internal sampler states are signal values and do not correspond to actual samplers. */
BLI_assert(sampler_state.type != GPU_SAMPLER_STATE_TYPE_INTERNAL);
if (sampler_state.type == GPU_SAMPLER_STATE_TYPE_CUSTOM) {
return custom_samplers_state_cache_[sampler_state.custom_type];
}
return samplers_state_cache_[sampler_state.extend_yz][sampler_state.extend_x]
[sampler_state.filtering];
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Proxy texture
*
* Dummy texture to see if the implementation supports the requested size.
* \{ */
bool GLTexture::proxy_check(int mip)
{
/* NOTE: This only checks if this mipmap is valid / supported.
* TODO(fclem): make the check cover the whole mipmap chain. */
/* Manual validation first, since some implementation have issues with proxy creation. */
int max_size = GPU_max_texture_size();
int max_3d_size = GPU_max_texture_3d_size();
int max_cube_size = GLContext::max_cubemap_size;
int size[3] = {1, 1, 1};
this->mip_size_get(mip, size);
if (type_ & GPU_TEXTURE_ARRAY) {
if (this->layer_count() > GPU_max_texture_layers()) {
return false;
}
}
if (type_ == GPU_TEXTURE_3D) {
if (size[0] > max_3d_size || size[1] > max_3d_size || size[2] > max_3d_size) {
return false;
}
}
else if ((type_ & ~GPU_TEXTURE_ARRAY) == GPU_TEXTURE_2D) {
if (size[0] > max_size || size[1] > max_size) {
return false;
}
}
else if ((type_ & ~GPU_TEXTURE_ARRAY) == GPU_TEXTURE_1D) {
if (size[0] > max_size) {
return false;
}
}
else if ((type_ & ~GPU_TEXTURE_ARRAY) == GPU_TEXTURE_CUBE) {
if (size[0] > max_cube_size) {
return false;
}
}
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_WIN, GPU_DRIVER_ANY) ||
GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OFFICIAL))
{
/* Some AMD drivers have a faulty `GL_PROXY_TEXTURE_..` check.
* (see #55888, #56185, #59351).
* Checking with `GL_PROXY_TEXTURE_..` doesn't prevent `Out Of Memory` issue,
* it just states that the OGL implementation can support the texture.
* So we already manually check the maximum size and maximum number of layers.
* Same thing happens on Nvidia/macOS 10.15 (#78175). */
return true;
}
GLenum gl_proxy = to_gl_proxy(type_);
GLenum internal_format = to_gl_internal_format(format_);
GLenum gl_format = to_gl_data_format(format_);
GLenum gl_type = to_gl(to_texture_data_format(format_));
/* Small exception. */
int dimensions = (type_ == GPU_TEXTURE_CUBE) ? 2 : this->dimensions_count();
if (format_flag_ & GPU_FORMAT_COMPRESSED) {
size_t img_size = ((size[0] + 3) / 4) * ((size[1] + 3) / 4) * to_block_size(format_);
switch (dimensions) {
default:
case 1:
glCompressedTexImage1D(gl_proxy, mip, size[0], 0, gl_format, img_size, nullptr);
break;
case 2:
glCompressedTexImage2D(gl_proxy, mip, UNPACK2(size), 0, gl_format, img_size, nullptr);
break;
case 3:
glCompressedTexImage3D(gl_proxy, mip, UNPACK3(size), 0, gl_format, img_size, nullptr);
break;
}
}
else {
switch (dimensions) {
default:
case 1:
glTexImage1D(gl_proxy, mip, internal_format, size[0], 0, gl_format, gl_type, nullptr);
break;
case 2:
glTexImage2D(
gl_proxy, mip, internal_format, UNPACK2(size), 0, gl_format, gl_type, nullptr);
break;
case 3:
glTexImage3D(
gl_proxy, mip, internal_format, UNPACK3(size), 0, gl_format, gl_type, nullptr);
break;
}
}
int width = 0;
glGetTexLevelParameteriv(gl_proxy, 0, GL_TEXTURE_WIDTH, &width);
return (width > 0);
}
/** \} */
void GLTexture::check_feedback_loop()
{
/* Do not check if using compute shader. */
GLShader *sh = dynamic_cast<GLShader *>(Context::get()->shader);
if (sh && sh->is_compute()) {
return;
}
GLFrameBuffer *fb = static_cast<GLFrameBuffer *>(GLContext::get()->active_fb);
for (int i = 0; i < ARRAY_SIZE(fb_); i++) {
if (fb_[i] == fb) {
GPUAttachmentType type = fb_attachment_[i];
GPUAttachment attachment = fb->attachments_[type];
/* Check for when texture is used with texture barrier. */
GPUAttachment attachment_read = fb->tmp_detached_[type];
if (attachment.mip <= mip_max_ && attachment.mip >= mip_min_ &&
attachment_read.tex == nullptr)
{
char msg[256];
SNPRINTF(msg,
"Feedback loop: Trying to bind a texture (%s) with mip range %d-%d but mip %d is "
"attached to the active framebuffer (%s)",
name_.c_str(),
mip_min_,
mip_max_,
attachment.mip,
fb->name_);
debug::raise_gl_error(msg);
}
return;
}
}
}
/* -------------------------------------------------------------------- */
/** \name Pixel Buffer
* \{ */
GLPixelBuffer::GLPixelBuffer(size_t 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, nullptr, 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);
}
GPUPixelBufferNativeHandle GLPixelBuffer::get_native_handle()
{
GPUPixelBufferNativeHandle native_handle;
native_handle.handle = int64_t(gl_id_);
native_handle.size = size_;
return native_handle;
}
size_t GLPixelBuffer::get_size()
{
return size_;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,386 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "BLI_assert.h"
#include "gpu_texture_private.hh"
namespace blender::gpu {
class GLTexturePool;
class GLTexture : public Texture {
friend class GLStateManager;
friend class GLFrameBuffer;
friend class GLTexturePool;
private:
/**
* A cache of all possible sampler configurations stored along each of the three axis of
* variation. The first and second variation axis are the wrap mode along x and y axis
* respectively, and the third variation axis is the filtering type. See the samplers_init()
* method for more information.
*/
static GLuint samplers_state_cache_[GPU_SAMPLER_EXTEND_MODES_COUNT]
[GPU_SAMPLER_EXTEND_MODES_COUNT]
[GPU_SAMPLER_FILTERING_TYPES_COUNT];
static const int samplers_state_cache_count_ = GPU_SAMPLER_EXTEND_MODES_COUNT *
GPU_SAMPLER_EXTEND_MODES_COUNT *
GPU_SAMPLER_FILTERING_TYPES_COUNT;
/**
* A cache of all custom sampler configurations described in GPUSamplerCustomType. See the
* samplers_init() method for more information.
*/
static GLuint custom_samplers_state_cache_[GPU_SAMPLER_CUSTOM_TYPES_COUNT];
/** Target to bind the texture to (#GL_TEXTURE_1D, #GL_TEXTURE_2D, etc...). */
GLenum target_ = -1;
/** opengl identifier for texture. */
GLuint tex_id_ = 0;
/** Legacy workaround for texture copy. Created when using framebuffer_get(). */
FrameBuffer *framebuffer_ = nullptr;
/** True if this texture is bound to at least one texture unit. */
/* TODO(fclem): How do we ensure thread safety here? */
bool is_bound_ = false;
/** Same as is_bound_ but for image slots. */
bool is_bound_image_ = false;
/** True if pixels in the texture have been initialized. */
bool has_pixels_ = false;
public:
GLTexture(const char *name);
~GLTexture();
void update_sub(int mip,
int offset[3],
int extent[3],
eGPUDataFormat type,
const void *data,
const uint unpack_row_length = 0) 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.
*
* \warning Depth textures are not populated but they have their mips correctly defined.
* \warning This resets the mipmap range.
*/
void generate_mipmap() override;
void copy_to(Texture *dst, IndexRange mip_levels) override;
void clear(const double4 data) override;
void swizzle_set(const char swizzle_mask[4]) override;
void mip_range_set(int min, int max) override;
void read(int mip, eGPUDataFormat type, void *data) override;
void check_feedback_loop();
/**
* Pre-generate, setup all possible samplers and cache them in the samplers_state_cache_ and
* custom_samplers_state_cache_ arrays. This is done to avoid the runtime cost associated with
* setting up a sampler at draw time.
*/
static void samplers_init();
/**
* Free the samplers cache generated in samplers_init() method.
*/
static void samplers_free();
/**
* Get the handle of the OpenGL sampler that corresponds to the given sampler state.
* The sampler is retrieved from the cached samplers computed in the samplers_init() method.
*/
static GLuint get_sampler(const GPUSamplerState &sampler_state);
protected:
/** Return true on success. */
bool init_internal() override;
/** Return true on success. */
bool init_internal(VertBuf *vbo) override;
/** Return true on success. */
bool init_internal(gpu::Texture *src,
int mip_offset,
int layer_offset,
bool use_stencil) override;
private:
bool proxy_check(int mip);
void stencil_texture_mode_set(bool use_stencil);
void update_sub_direct_state_access(
int mip, int offset[3], int extent[3], GLenum gl_format, GLenum gl_type, const void *data);
FrameBuffer *framebuffer_get();
MEM_CXX_CLASS_ALLOC_FUNCS("GLTexture")
};
class GLPixelBuffer : public PixelBuffer {
private:
GLuint gl_id_ = 0;
public:
GLPixelBuffer(size_t size);
~GLPixelBuffer();
void *map() override;
void unmap() override;
GPUPixelBufferNativeHandle get_native_handle() override;
size_t get_size() override;
MEM_CXX_CLASS_ALLOC_FUNCS("GLPixelBuffer")
};
inline GLenum to_gl_internal_format(TextureFormat format)
{
#define CASE(a, b, c, blender_enum, d, e, f, gl_pixel_enum, h) \
case TextureFormat::blender_enum: \
return GL_##gl_pixel_enum;
switch (format) {
GPU_TEXTURE_FORMAT_EXPAND(CASE)
case TextureFormat::Invalid:
break;
}
#undef CASE
BLI_assert_msg(0, "Texture format incorrect or unsupported");
return 0;
}
inline GLenum to_gl_target(GPUTextureType type)
{
switch (type) {
case GPU_TEXTURE_1D:
return GL_TEXTURE_1D;
case GPU_TEXTURE_1D_ARRAY:
return GL_TEXTURE_1D_ARRAY;
case GPU_TEXTURE_2D:
return GL_TEXTURE_2D;
case GPU_TEXTURE_2D_ARRAY:
return GL_TEXTURE_2D_ARRAY;
case GPU_TEXTURE_3D:
return GL_TEXTURE_3D;
case GPU_TEXTURE_CUBE:
return GL_TEXTURE_CUBE_MAP;
case GPU_TEXTURE_CUBE_ARRAY:
return GL_TEXTURE_CUBE_MAP_ARRAY_ARB;
case GPU_TEXTURE_BUFFER:
return GL_TEXTURE_BUFFER;
default:
BLI_assert(0);
return GL_TEXTURE_1D;
}
}
inline GLenum to_gl_proxy(GPUTextureType type)
{
switch (type) {
case GPU_TEXTURE_1D:
return GL_PROXY_TEXTURE_1D;
case GPU_TEXTURE_1D_ARRAY:
return GL_PROXY_TEXTURE_1D_ARRAY;
case GPU_TEXTURE_2D:
return GL_PROXY_TEXTURE_2D;
case GPU_TEXTURE_2D_ARRAY:
return GL_PROXY_TEXTURE_2D_ARRAY;
case GPU_TEXTURE_3D:
return GL_PROXY_TEXTURE_3D;
case GPU_TEXTURE_CUBE:
return GL_PROXY_TEXTURE_CUBE_MAP;
case GPU_TEXTURE_CUBE_ARRAY:
return GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB;
case GPU_TEXTURE_BUFFER:
default:
BLI_assert(0);
return GL_TEXTURE_1D;
}
}
inline GLenum swizzle_to_gl(const char swizzle)
{
switch (swizzle) {
default:
case 'x':
case 'r':
return GL_RED;
case 'y':
case 'g':
return GL_GREEN;
case 'z':
case 'b':
return GL_BLUE;
case 'w':
case 'a':
return GL_ALPHA;
case '0':
return GL_ZERO;
case '1':
return GL_ONE;
}
}
inline GLenum to_gl(eGPUDataFormat format)
{
switch (format) {
case GPU_DATA_FLOAT:
return GL_FLOAT;
case GPU_DATA_INT:
return GL_INT;
case GPU_DATA_UINT:
return GL_UNSIGNED_INT;
case GPU_DATA_UBYTE:
return GL_UNSIGNED_BYTE;
case GPU_DATA_UINT_24_8_DEPRECATED:
return GL_UNSIGNED_INT_24_8;
case GPU_DATA_2_10_10_10_REV:
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;
}
}
inline GLenum to_gl_data_format(TextureFormat format)
{
switch (format) {
/* Texture & Render-Buffer Formats. */
case TextureFormat::UNORM_8_8_8_8:
case TextureFormat::SFLOAT_32_32_32_32:
case TextureFormat::SFLOAT_16_16_16_16:
case TextureFormat::UNORM_16_16_16_16:
return GL_RGBA;
case TextureFormat::UINT_8_8_8_8:
case TextureFormat::SINT_8_8_8_8:
case TextureFormat::SINT_32_32_32_32:
case TextureFormat::UINT_32_32_32_32:
case TextureFormat::UINT_16_16_16_16:
case TextureFormat::SINT_16_16_16_16:
return GL_RGBA_INTEGER;
case TextureFormat::UNORM_8_8:
case TextureFormat::SFLOAT_32_32:
case TextureFormat::SFLOAT_16_16:
case TextureFormat::UNORM_16_16:
return GL_RG;
case TextureFormat::UINT_8_8:
case TextureFormat::SINT_8_8:
case TextureFormat::UINT_32_32:
case TextureFormat::SINT_32_32:
case TextureFormat::SINT_16_16:
case TextureFormat::UINT_16_16:
return GL_RG_INTEGER;
case TextureFormat::UNORM_8:
case TextureFormat::SFLOAT_32:
case TextureFormat::SFLOAT_16:
case TextureFormat::UNORM_16:
return GL_RED;
case TextureFormat::UINT_8:
case TextureFormat::SINT_8:
case TextureFormat::UINT_32:
case TextureFormat::SINT_32:
case TextureFormat::UINT_16:
case TextureFormat::SINT_16:
return GL_RED_INTEGER;
/* Special formats texture & render-buffer. */
case TextureFormat::UINT_10_10_10_2:
case TextureFormat::UNORM_10_10_10_2:
case TextureFormat::SRGBA_8_8_8_8:
return GL_RGBA;
case TextureFormat::UFLOAT_11_11_10:
return GL_RGB;
case TextureFormat::SFLOAT_32_DEPTH_UINT_8:
return GL_DEPTH_STENCIL;
/* Texture only formats. */
case TextureFormat::SNORM_16_16_16_16:
case TextureFormat::SNORM_8_8_8_8:
return GL_RGBA;
case TextureFormat::SFLOAT_16_16_16:
case TextureFormat::SFLOAT_32_32_32:
case TextureFormat::SINT_32_32_32:
case TextureFormat::UINT_32_32_32:
case TextureFormat::SNORM_16_16_16:
case TextureFormat::SINT_16_16_16:
case TextureFormat::UINT_16_16_16:
case TextureFormat::UNORM_16_16_16:
case TextureFormat::SNORM_8_8_8:
case TextureFormat::UNORM_8_8_8:
case TextureFormat::SINT_8_8_8:
case TextureFormat::UINT_8_8_8:
return GL_RGB;
case TextureFormat::SNORM_16_16:
case TextureFormat::SNORM_8_8:
return GL_RG;
case TextureFormat::SNORM_16:
case TextureFormat::SNORM_8:
return GL_RED;
/* Special formats, texture only. */
case TextureFormat::SRGB_DXT1:
return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
case TextureFormat::SRGB_DXT3:
return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
case TextureFormat::SRGB_DXT5:
return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
case TextureFormat::SNORM_DXT1:
return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
case TextureFormat::SNORM_DXT3:
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
case TextureFormat::SNORM_DXT5:
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case TextureFormat::SRGBA_8_8_8:
case TextureFormat::UFLOAT_9_9_9_EXP_5:
return GL_RGB;
/* Depth Formats. */
case TextureFormat::SFLOAT_32_DEPTH:
case TextureFormat::UNORM_16_DEPTH:
return GL_DEPTH_COMPONENT;
case TextureFormat::Invalid:
break;
}
BLI_assert_msg(0, "Texture format incorrect or unsupported\n");
return 0;
}
/**
* Assume UNORM/Float target. Used with #glReadPixels.
*/
inline GLenum channel_len_to_gl(int channel_len)
{
switch (channel_len) {
case 1:
return GL_RED;
case 2:
return GL_RG;
case 3:
return GL_RGB;
case 4:
return GL_RGBA;
default:
BLI_assert_msg(0, "Wrong number of texture channels");
return GL_RED;
}
}
BLI_INLINE GLTexture *unwrap(Texture *tex)
{
return static_cast<GLTexture *>(tex);
}
BLI_INLINE Texture *wrap(GLTexture *texture)
{
return static_cast<Texture *>(texture);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,289 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_string.h"
#include "gl_backend.hh"
#include "gl_texture_pool.hh"
#include "CLG_log.h"
#include "fmt/format.h"
namespace blender::gpu {
static CLG_LogRef LOG = {"gpu.opengl"};
/* Given a TextureFormat, return an underlying format on which to alias. If the
* format does not support aliasing to another format, simply return the input. */
static TextureFormat get_compatible_texture_format(TextureFormat format)
{
/* glTextureView doesn't support aliasing on depth, stencil, or most compressed formats. */
GPUTextureFormatFlag format_flag = to_format_flag(format);
if (bool(format_flag & GPU_FORMAT_DEPTH_STENCIL)) {
return format;
}
if (bool(format_flag & GPU_FORMAT_COMPRESSED)) {
return format;
}
/* Given expected byte size, we use a default format available as write/target format. */
switch (to_bytesize(format)) {
case 16:
return TextureFormat::SFLOAT_32_32_32_32;
case 8:
return TextureFormat::SFLOAT_32_32;
case 4:
return TextureFormat::SFLOAT_32;
case 2:
return TextureFormat::SFLOAT_16;
case 1:
return TextureFormat::UINT_8;
default:
return TextureFormat::Invalid;
}
}
GLTexturePool::~GLTexturePool()
{
for (const TextureHandle &handle : acquired_) {
release_texture(wrap(handle.view));
}
for (AllocationHandle &handle : pool_) {
GPU_texture_free(handle.texture);
}
}
Texture *GLTexturePool::acquire_texture_impl(int3 extent,
int mip_len,
GPUTextureType type,
TextureFormat format,
eGPUTextureUsage usage,
const char *name)
{
/* Determine format of compatible underlying texture. If there is no
* compatible format to alias upon, we simply require an exact match
* for the underlying texture. */
TextureFormat compatible_format = get_compatible_texture_format(format);
BLI_assert(compatible_format != TextureFormat::Invalid);
/* Determine actual mipmap depth. */
int mip_len_max = 1 + floorf(log2f(std::max({extent.x, extent.y, extent.z})));
mip_len = min_ii(mip_len, mip_len_max);
/* Search for the first compatible existing texture. */
int64_t match_index = -1;
for (uint64_t i : pool_.index_range()) {
const AllocationHandle &handle = pool_[i];
if (handle.texture->format_ != compatible_format) {
continue;
}
if (int3(handle.texture->w_, handle.texture->h_, handle.texture->d_) != extent) {
/* TODO(not_mark): sub-view on `texture->d_`. */
continue;
}
if (handle.texture->mip_count() != mip_len) {
/* TODO(not_mark): sub-view on mip levels. */
continue;
}
match_index = i;
break;
}
/* Return value. */
TextureHandle texture_handle;
/* Acquire the compatible texture, or create a new one as a last resort. */
if (match_index != -1) {
texture_handle.texture = pool_[match_index].texture;
pool_.remove_and_reorder(match_index);
/* Override the usage. It doesn't really apply to OpenGL in practice, but `GPU_texture_usage`
* callers may still rely on it. */
texture_handle.texture->usage_set(usage | GPU_TEXTURE_USAGE_FORMAT_VIEW);
}
else {
/* Debug label attached to allocated texture object. */
std::string texture_name_str;
if (G.debug & G_DEBUG_GPU) {
texture_name_str = fmt::format("TexFromPool_{}", pool_.size());
}
Texture *texture = GPUBackend::get()->texture_alloc(texture_name_str.c_str());
texture->usage_set(usage | GPU_TEXTURE_USAGE_FORMAT_VIEW);
bool texture_result = false;
UNUSED_VARS_NDEBUG(texture_result);
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_1D_ARRAY:
texture_result = texture->init_1D(extent.x, extent.y, mip_len, compatible_format);
break;
case GPU_TEXTURE_2D:
case GPU_TEXTURE_2D_ARRAY:
texture_result = texture->init_2D(
extent.x, extent.y, extent.z, mip_len, compatible_format);
break;
case GPU_TEXTURE_3D:
texture_result = texture->init_3D(
extent.x, extent.y, extent.z, mip_len, compatible_format);
break;
case GPU_TEXTURE_CUBE:
case GPU_TEXTURE_CUBE_ARRAY:
texture_result = texture->init_cubemap(extent.x, extent.y, mip_len, compatible_format);
break;
default:
BLI_assert_unreachable();
break;
}
BLI_assert(texture_result);
texture_handle.texture = unwrap(texture);
}
/* On acquire, issue barriers; backing texture or view may still be in flight somewhere. */
GPUBarrier barrier = {};
if (usage & GPU_TEXTURE_USAGE_SHADER_READ) {
barrier |= (GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) {
barrier |= GPU_BARRIER_SHADER_IMAGE_ACCESS;
}
if (usage & GPU_TEXTURE_USAGE_ATTACHMENT) {
barrier |= GPU_BARRIER_FRAMEBUFFER;
}
GPU_memory_barrier(barrier);
/* Debug label attached to view texture object. */
std::string view_name_str;
if (G.debug & G_DEBUG_GPU) {
view_name_str = name ? name : texture_handle.texture->name_;
}
/* Assemble texture view and add to handle. Note, glTextureView with identical formats is
* allowed, even if the formats are not listed for aliasing in the Internal Formats table. */
Texture *view = GPUBackend::get()->texture_alloc(view_name_str.c_str());
bool view_result = false;
UNUSED_VARS_NDEBUG(view_result);
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_2D:
case GPU_TEXTURE_3D:
case GPU_TEXTURE_CUBE:
view_result = view->init_view(
texture_handle.texture, format, type, 0, mip_len, 0, 1, false, false);
break;
case GPU_TEXTURE_1D_ARRAY:
view_result = view->init_view(
texture_handle.texture, format, type, 0, mip_len, 0, extent.y, false, false);
break;
case GPU_TEXTURE_2D_ARRAY:
case GPU_TEXTURE_CUBE_ARRAY:
view_result = view->init_view(
texture_handle.texture, format, type, 0, mip_len, 0, extent.z, false, false);
break;
default:
BLI_assert_unreachable();
break;
}
BLI_assert(view_result);
/* On integer textures, disable filtering by default, as this is not guaranteed to be
* consistently supported across backends. */
if (GPU_texture_has_integer_format(view)) {
view->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, false);
view->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_MIPMAP, false);
view->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_ANISOTROPIC_MASK,
false);
}
texture_handle.view = unwrap(view);
if (G.debug & G_DEBUG_GPU) {
current_usage_data_.usage_count++;
current_usage_data_.usage_count_max = std::max(current_usage_data_.usage_count,
current_usage_data_.usage_count_max);
}
acquired_.add(texture_handle);
return wrap(texture_handle.view);
}
void GLTexturePool::release_texture(Texture *tex)
{
BLI_assert_msg(acquired_.contains({unwrap(tex)}),
"Unacquired texture passed to TexturePool::release_texture()");
TextureHandle texture_handle = acquired_.lookup_key({unwrap(tex), {}, 1});
if (G.debug & G_DEBUG_GPU) {
current_usage_data_.usage_count--;
}
/* Move allocation back to `pool_`. */
AllocationHandle allocation_handle;
allocation_handle.texture = texture_handle.texture;
pool_.append(allocation_handle);
/* Destroy view and handle, if a view was created. */
GPU_texture_free(texture_handle.view);
acquired_.remove(texture_handle);
}
void GLTexturePool::offset_users_count(Texture *tex, int offset)
{
BLI_assert_msg(acquired_.contains({unwrap(tex)}),
"Unacquired texture passed to TexturePool::offset_users_count()");
TextureHandle texture_handle = acquired_.lookup_key({unwrap(tex), {}, 1});
texture_handle.users_count += offset;
acquired_.add_overwrite(texture_handle);
}
void GLTexturePool::reset(bool force_free)
{
#ifndef NDEBUG
/* Iterate acquired textures, and ensure the internal counter equals 0; otherwise
* this indicates a missing `::retain()` or `::release()`. */
for (const TextureHandle &tex : acquired_) {
BLI_assert_msg(tex.users_count == 0,
"Missing texture release/retain. Likely TextureFromPool::release(), "
"TextureFromPool::retain() or TexturePool::release_texture().");
}
#endif
/* Reverse iterate unused allocations, to make sure we only reorder known good handles. */
for (int i = pool_.size() - 1; i >= 0; i--) {
AllocationHandle &handle = pool_[i];
if (handle.unused_cycles_count >= max_unused_cycles_ || force_free) {
GPU_texture_free(handle.texture);
pool_.remove_and_reorder(i);
}
else {
handle.unused_cycles_count++;
}
}
if (G.debug & G_DEBUG_GPU) {
/* Log debug usage if it differs from the last reset. */
if (!(previous_usage_data_ == current_usage_data_)) {
log_usage_data();
}
/* Reset usage data to track it for the next reset. */
previous_usage_data_ = current_usage_data_;
current_usage_data_ = {};
current_usage_data_.usage_count = acquired_.size();
}
}
void GLTexturePool::log_usage_data() const
{
int64_t total_texture_count = acquired_.size() + pool_.size();
CLOG_TRACE(&LOG,
"GLTexturePool uses %ld textures (%ld consecutively)",
static_cast<long>(total_texture_count),
static_cast<long>(current_usage_data_.usage_count_max));
}
} // namespace blender::gpu

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gl_texture.hh"
#include "gpu_texture_pool_private.hh"
namespace blender::gpu {
class GLTexturePool : public TexturePoolBase {
/* Defer deallocation enough cycles to avoid interleaved calls to different viewport render
* functions (selection / display) causing constant allocation / deallocation (See #113024). */
static constexpr int max_unused_cycles_ = 8;
struct AllocationHandle {
GLTexture *texture = nullptr;
/* Counter to track the number of unused cycles before deallocation in `pool_`. */
int unused_cycles_count = 0;
};
struct TextureHandle {
GLTexture *view = nullptr;
GLTexture *texture = nullptr;
/* Counter to track texture acquire/retain mismatches in `acquire_`. */
int users_count = 1;
/* We use the pointer as hash/comparator, as a texture cannot be acquired twice. */
uint64_t hash() const
{
return get_default_hash(view);
}
bool operator==(const TextureHandle &o) const
{
return view == o.view;
}
};
Vector<AllocationHandle> pool_;
Set<TextureHandle> acquired_;
/* Debug storage to log memory usage. Log is only output
* if values have changed since the last `::reset()`. */
struct LogUsageData {
int64_t usage_count = 0;
int64_t usage_count_max = 0;
bool operator==(const LogUsageData &o) const
{
return std::tie(usage_count, usage_count_max) == std::tie(o.usage_count, o.usage_count_max);
}
};
LogUsageData previous_usage_data_ = {};
LogUsageData current_usage_data_ = {};
/* Output usage data to debug log. Called on `--debug-gpu` */
void log_usage_data() const;
protected:
Texture *acquire_texture_impl(int3 extent,
int mip_len,
GPUTextureType type,
TextureFormat format,
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_GENERAL,
const char *name = nullptr) override;
public:
~GLTexturePool();
void release_texture(Texture *tex) override;
void reset(bool force_free = false) override;
void offset_users_count(Texture *tex, int offset) override;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,158 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_string.h"
#include "GPU_capabilities.hh"
#include "gpu_context_private.hh"
#include "gl_debug.hh"
#include "gl_texture.hh"
#include "gl_uniform_buffer.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
GLUniformBuf::GLUniformBuf(size_t size, const char *name) : UniformBuf(size, name)
{
/* Do not create ubo GL buffer here to allow allocation from any thread. */
BLI_assert(size <= GPU_max_uniform_buffer_size());
}
GLUniformBuf::~GLUniformBuf()
{
GLContext::buffer_free(ubo_id_);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Data upload / update
* \{ */
void GLUniformBuf::init()
{
BLI_assert(GLContext::get());
glGenBuffers(1, &ubo_id_);
glBindBuffer(GL_UNIFORM_BUFFER, ubo_id_);
glBufferData(GL_UNIFORM_BUFFER, size_in_bytes_, nullptr, GL_DYNAMIC_DRAW);
debug::object_label(GL_UNIFORM_BUFFER, ubo_id_, name_);
}
void GLUniformBuf::update(const void *data)
{
if (ubo_id_ == 0) {
this->init();
}
glBindBuffer(GL_UNIFORM_BUFFER, ubo_id_);
glBufferSubData(GL_UNIFORM_BUFFER, 0, size_in_bytes_, data);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
void GLUniformBuf::clear_to_zero()
{
if (ubo_id_ == 0) {
this->init();
}
uint32_t data = 0;
TextureFormat internal_format = TextureFormat::UINT_32;
eGPUDataFormat data_format = GPU_DATA_UINT;
if (GLContext::direct_state_access_support) {
glClearNamedBufferData(ubo_id_,
to_gl_internal_format(internal_format),
to_gl_data_format(internal_format),
to_gl(data_format),
&data);
}
else {
/* WATCH(@fclem): This should be ok since we only use clear outside of drawing functions. */
glBindBuffer(GL_UNIFORM_BUFFER, ubo_id_);
glClearBufferData(GL_UNIFORM_BUFFER,
to_gl_internal_format(internal_format),
to_gl_data_format(internal_format),
to_gl(data_format),
&data);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Usage
* \{ */
void GLUniformBuf::bind(int slot)
{
if (slot >= GLContext::max_ubo_binds) {
fprintf(
stderr,
"Error: Trying to bind \"%s\" ubo to slot %d which is above the reported limit of %d.\n",
name_,
slot,
GLContext::max_ubo_binds);
return;
}
if (ubo_id_ == 0) {
this->init();
}
if (data_ != nullptr) {
this->update(data_);
MEM_SAFE_DELETE_VOID(data_);
}
slot_ = slot;
glBindBufferBase(GL_UNIFORM_BUFFER, slot_, ubo_id_);
#ifndef NDEBUG
BLI_assert(slot < 16);
GLContext::get()->bound_ubo_slots |= 1 << slot;
#endif
}
void GLUniformBuf::bind_as_ssbo(int slot)
{
if (ubo_id_ == 0) {
this->init();
}
if (data_ != nullptr) {
this->update(data_);
MEM_SAFE_DELETE_VOID(data_);
}
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, ubo_id_);
#ifndef NDEBUG
BLI_assert(slot < 16);
GLContext::get()->bound_ssbo_slots |= 1 << slot;
#endif
}
void GLUniformBuf::unbind()
{
#ifndef NDEBUG
/* NOTE: This only unbinds the last bound slot. */
glBindBufferBase(GL_UNIFORM_BUFFER, slot_, 0);
/* Hope that the context did not change. */
GLContext::get()->bound_ubo_slots &= ~(1 << slot_);
#endif
slot_ = 0;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "gpu_uniform_buffer_private.hh"
namespace blender::gpu {
/**
* Implementation of Uniform Buffers using OpenGL.
*/
class GLUniformBuf : public UniformBuf {
private:
/** Slot to which this UBO is currently bound. -1 if not bound. */
int slot_ = -1;
/** OpenGL Object handle. */
GLuint ubo_id_ = 0;
public:
GLUniformBuf(size_t size, const char *name);
~GLUniformBuf();
void update(const void *data) override;
void clear_to_zero() override;
void bind(int slot) override;
void bind_as_ssbo(int slot) override;
void unbind() override;
private:
void init();
MEM_CXX_CLASS_ALLOC_FUNCS("GLUniformBuf");
};
} // namespace blender::gpu

View File

@@ -0,0 +1,132 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "GPU_vertex_buffer.hh"
#include "gpu_shader_interface.hh"
#include "gl_batch.hh"
#include "gl_context.hh"
#include "gl_index_buffer.hh"
#include "gl_storage_buffer.hh"
#include "gl_vertex_buffer.hh"
#include "gl_vertex_array.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Vertex Array Bindings
* \{ */
/** Returns enabled vertex pointers as a bit-flag (one bit per attribute). */
static uint16_t vbo_bind(const ShaderInterface *interface,
const GPUVertFormat *format,
uint v_first,
uint v_len,
const bool use_instancing)
{
uint16_t enabled_attrib = 0;
const uint attr_len = format->attr_len;
uint stride = format->stride;
uint offset = 0;
GLuint divisor = (use_instancing) ? 1 : 0;
for (uint a_idx = 0; a_idx < attr_len; a_idx++) {
const GPUVertAttr *a = &format->attrs[a_idx];
if (format->deinterleaved) {
offset += ((a_idx == 0) ? 0 : format->attrs[a_idx - 1].type.size()) * v_len;
stride = a->type.size();
}
else {
offset = a->offset;
}
/* This is in fact an offset in memory. */
const GLvoid *pointer = reinterpret_cast<const GLubyte *>(intptr_t(offset + v_first * stride));
const GLenum type = to_gl(a->type.comp_type());
for (uint n_idx = 0; n_idx < a->name_len; n_idx++) {
const char *name = GPU_vertformat_attr_name_get(format, a, n_idx);
const ShaderInput *input = interface->attr_get(name);
if (input == nullptr || input->location == -1) {
continue;
}
enabled_attrib |= (1 << input->location);
glEnableVertexAttribArray(input->location);
glVertexAttribDivisor(input->location, divisor);
switch (a->type.fetch_mode()) {
case GPU_FETCH_FLOAT:
case GPU_FETCH_INT_TO_FLOAT_UNIT:
glVertexAttribPointer(
input->location, a->type.comp_len(), type, GL_TRUE, stride, pointer);
break;
case GPU_FETCH_INT:
glVertexAttribIPointer(input->location, a->type.comp_len(), type, stride, pointer);
break;
}
}
}
return enabled_attrib;
}
void GLVertArray::update_bindings(const GLuint vao,
const Batch *batch_, /* Should be GLBatch. */
const ShaderInterface *interface)
{
const GLBatch *batch = static_cast<const GLBatch *>(batch_);
uint16_t attr_mask = interface->enabled_attr_mask_;
glBindVertexArray(vao);
/* Reverse order so first VBO'S have more prevalence (in term of attribute override). */
for (int v = GPU_BATCH_VBO_MAX_LEN - 1; v > -1; v--) {
GLVertBuf *vbo = batch->verts_(v);
if (vbo) {
vbo->bind();
attr_mask &= ~vbo_bind(interface, &vbo->format, 0, vbo->vertex_len, false);
}
}
if (attr_mask != 0) {
for (uint16_t mask = 1, a = 0; a < 16; a++, mask <<= 1) {
if (attr_mask & mask) {
GLContext *ctx = GLContext::get();
/* This replaces glVertexAttrib4f(a, 0.0f, 0.0f, 0.0f, 1.0f); with a more modern style.
* Fix issues for some drivers (see #75069). */
glBindVertexBuffer(a, ctx->default_attr_vbo_, intptr_t(0), intptr_t(0));
glEnableVertexAttribArray(a);
glVertexAttribFormat(a, 4, GL_FLOAT, GL_FALSE, 0);
glVertexAttribBinding(a, a);
}
}
}
if (batch->elem) {
/* Binds the index buffer. This state is also saved in the VAO. */
static_cast<GLIndexBuf *>(batch->elem)->bind();
}
}
void GLVertArray::update_bindings(const GLuint vao,
const uint v_first,
const GPUVertFormat *format,
const ShaderInterface *interface)
{
glBindVertexArray(vao);
vbo_bind(interface, format, v_first, 0, false);
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "GPU_batch.hh"
#include "gl_shader_interface.hh"
namespace blender::gpu::GLVertArray {
/**
* Update the Attribute Binding of the currently bound VAO.
*/
void update_bindings(const GLuint vao, const Batch *batch, const ShaderInterface *interface);
/**
* Another version of update_bindings for Immediate mode.
*/
void update_bindings(const GLuint vao,
uint v_first,
const GPUVertFormat *format,
const ShaderInterface *interface);
} // namespace blender::gpu::GLVertArray

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "GPU_texture.hh"
#include "gl_context.hh"
#include "gl_vertex_buffer.hh"
namespace blender::gpu {
void GLVertBuf::acquire_data()
{
if (usage_ == GPU_USAGE_DEVICE_ONLY) {
return;
}
/* Discard previous data if any. */
MEM_SAFE_DELETE(data_);
data_ = MEM_new_array_uninitialized<uchar>(this->size_alloc_get(), __func__);
}
void GLVertBuf::resize_data()
{
if (usage_ == GPU_USAGE_DEVICE_ONLY) {
return;
}
data_ = static_cast<uchar *>(
MEM_realloc_uninitialized(data_, sizeof(uchar) * this->size_alloc_get()));
}
void GLVertBuf::release_data()
{
if (is_wrapper_) {
return;
}
if (vbo_id_ != 0) {
GPU_TEXTURE_FREE_SAFE(buffer_texture_);
GLContext::buffer_free(vbo_id_);
vbo_id_ = 0;
memory_usage -= vbo_size_;
}
MEM_SAFE_DELETE(data_);
}
void GLVertBuf::upload_data()
{
this->bind();
}
void GLVertBuf::bind()
{
BLI_assert(GLContext::get() != nullptr);
if (vbo_id_ == 0) {
glGenBuffers(1, &vbo_id_);
}
glBindBuffer(GL_ARRAY_BUFFER, vbo_id_);
if (flag & GPU_VERTBUF_DATA_DIRTY) {
vbo_size_ = this->size_used_get();
/* This is fine on some systems but will crash on others. */
BLI_assert(vbo_size_ != 0);
/* Orphan the vbo to avoid sync then upload data. */
glBufferData(GL_ARRAY_BUFFER, ceil_to_multiple_ul(vbo_size_, 16), nullptr, to_gl(usage_));
/* Do not transfer data from host to device when buffer is device only. */
if (usage_ != GPU_USAGE_DEVICE_ONLY) {
glBufferSubData(GL_ARRAY_BUFFER, 0, vbo_size_, data_);
}
memory_usage += vbo_size_;
if (usage_ == GPU_USAGE_STATIC) {
MEM_SAFE_DELETE(data_);
}
flag &= ~GPU_VERTBUF_DATA_DIRTY;
flag |= GPU_VERTBUF_DATA_UPLOADED;
}
}
void GLVertBuf::bind_as_ssbo(uint binding)
{
bind();
BLI_assert(vbo_id_ != 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, vbo_id_);
#ifndef NDEBUG
BLI_assert(binding < 16);
GLContext::get()->bound_ssbo_slots |= 1 << binding;
#endif
}
void GLVertBuf::bind_as_texture(uint binding)
{
bind();
BLI_assert(vbo_id_ != 0);
if (buffer_texture_ == nullptr) {
buffer_texture_ = GPU_texture_create_from_vertbuf("vertbuf_as_texture", this);
}
GPU_texture_bind(buffer_texture_, binding);
}
void GLVertBuf::read(void *data) const
{
BLI_assert(is_active());
void *result = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
memcpy(data, result, size_used_get());
glUnmapBuffer(GL_ARRAY_BUFFER);
}
void GLVertBuf::wrap_handle(uint64_t handle)
{
BLI_assert(vbo_id_ == 0);
BLI_assert(glIsBuffer(uint(handle)));
is_wrapper_ = true;
vbo_id_ = uint(handle);
/* We assume the data is already on the device, so no need to allocate or send it. */
flag = GPU_VERTBUF_DATA_UPLOADED;
}
bool GLVertBuf::is_active() const
{
if (!vbo_id_) {
return false;
}
int active_vbo_id = 0;
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &active_vbo_id);
return vbo_id_ == active_vbo_id;
}
void GLVertBuf::update_sub(uint start, uint len, const void *data)
{
glBufferSubData(GL_ARRAY_BUFFER, start, len, data);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
namespace blender::gpu {
class GLVertBuf : public VertBuf {
friend class GLTexture; /* For buffer texture. */
friend class GLStorageBuf; /* For sub copy. */
private:
/** OpenGL buffer handle. Init on first upload. Immutable after that. */
GLuint vbo_id_ = 0;
/** Texture used if the buffer is bound as buffer texture. Init on first use. */
gpu::Texture *buffer_texture_ = nullptr;
/** Defines whether the buffer handle is wrapped by this GLVertBuf, i.e. we do not own it and
* should not free it. */
bool is_wrapper_ = false;
/** Size on the GPU. */
size_t vbo_size_ = 0;
public:
void bind();
void update_sub(uint start, uint len, const void *data) override;
void read(void *data) const override;
void wrap_handle(uint64_t handle) override;
protected:
void acquire_data() override;
void resize_data() override;
void release_data() override;
void upload_data() override;
void bind_as_ssbo(uint binding) override;
void bind_as_texture(uint binding) override;
private:
bool is_active() const;
MEM_CXX_CLASS_ALLOC_FUNCS("GLVertBuf");
};
static inline GLenum to_gl(GPUUsageType type)
{
switch (type) {
case GPU_USAGE_STREAM:
return GL_STREAM_DRAW;
case GPU_USAGE_DYNAMIC:
return GL_DYNAMIC_DRAW;
case GPU_USAGE_STATIC:
case GPU_USAGE_DEVICE_ONLY:
return GL_STATIC_DRAW;
default:
BLI_assert(0);
return GL_STATIC_DRAW;
}
}
static inline GLenum to_gl(GPUVertCompType type)
{
switch (type) {
case GPU_COMP_I8:
return GL_BYTE;
case GPU_COMP_U8:
return GL_UNSIGNED_BYTE;
case GPU_COMP_I16:
return GL_SHORT;
case GPU_COMP_U16:
return GL_UNSIGNED_SHORT;
case GPU_COMP_I32:
return GL_INT;
case GPU_COMP_U32:
return GL_UNSIGNED_INT;
case GPU_COMP_F32:
return GL_FLOAT;
case GPU_COMP_I10:
return GL_INT_2_10_10_10_REV;
default:
BLI_assert(0);
return GL_FLOAT;
}
}
} // namespace blender::gpu