taisei/src/renderer/gl33/texture.c
Andrei Alexeyev 5274cec283
gl33: use glTexStorage* and glInvalidateTexImage* when available
glTexStorage is mostly intended to work around a regression in ANGLE's
Metal backend; see https://issues.chromium.org/issues/355605685

Potentially fixes #386
2024-10-01 04:22:30 +02:00

654 lines
19 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "texture.h"
#include "../api.h"
#include "../glcommon/debug.h"
#include "gl33.h"
#include "opengl.h"
#include "util.h"
static GLenum class_to_gltarget(TextureClass cls) {
switch(cls) {
case TEXTURE_CLASS_2D:
return GL_TEXTURE_2D;
case TEXTURE_CLASS_CUBEMAP:
return GL_TEXTURE_CUBE_MAP;
default:
UNREACHABLE;
}
}
static GLenum linear_to_nearest(GLenum filter) {
switch(filter) {
case GL_LINEAR:
return GL_NEAREST;
case GL_LINEAR_MIPMAP_LINEAR:
case GL_LINEAR_MIPMAP_NEAREST:
return GL_NEAREST_MIPMAP_NEAREST;
default:
return filter;
}
}
static GLuint r_filter_to_gl_filter(TextureFilterMode mode, GLTextureFormatInfo *fmt_info) {
static GLuint map[] = {
[TEX_FILTER_LINEAR] = GL_LINEAR,
[TEX_FILTER_LINEAR_MIPMAP_NEAREST] = GL_LINEAR_MIPMAP_NEAREST,
[TEX_FILTER_LINEAR_MIPMAP_LINEAR] = GL_LINEAR_MIPMAP_LINEAR,
[TEX_FILTER_NEAREST] = GL_NEAREST,
[TEX_FILTER_NEAREST_MIPMAP_NEAREST] = GL_NEAREST_MIPMAP_NEAREST,
[TEX_FILTER_NEAREST_MIPMAP_LINEAR] = GL_NEAREST_MIPMAP_LINEAR,
};
assert((uint)mode < sizeof(map)/sizeof(*map));
GLenum filter = map[mode];
if(fmt_info->flags & GLTEX_FILTERABLE) {
return filter;
}
return linear_to_nearest(filter);
}
static GLuint r_wrap_to_gl_wrap(TextureWrapMode mode) {
static GLuint map[] = {
[TEX_WRAP_CLAMP] = GL_CLAMP_TO_EDGE,
[TEX_WRAP_MIRROR] = GL_MIRRORED_REPEAT,
[TEX_WRAP_REPEAT] = GL_REPEAT,
};
assert((uint)mode < sizeof(map)/sizeof(*map));
return map[mode];
}
typedef struct FormatFilterFlags {
GLTextureFormatFlags require, reject;
} FormatFilterFlags;
static GLTextureFormatInfo *pick_format(TextureType type, TextureFlags flags) {
GLTextureFormatMatchConfig cfg = { 0 };
cfg.intended_type = type;
cfg.flags.desirable |= GLTEX_FILTERABLE;
if(TEX_TYPE_IS_COMPRESSED(type)) {
cfg.flags.required |= GLTEX_COMPRESSED;
} else {
cfg.flags.forbidden |= GLTEX_COMPRESSED;
if(TEX_TYPE_IS_DEPTH(type)) {
cfg.flags.required |= GLTEX_DEPTH_RENDERABLE;
} else {
cfg.flags.required |= GLTEX_COLOR_RENDERABLE | GLTEX_BLENDABLE;
}
}
if(!TEX_TYPE_IS_DEPTH(type)) {
if(flags & TEX_FLAG_SRGB) {
cfg.flags.required |= GLTEX_SRGB;
} else {
cfg.flags.forbidden |= GLTEX_SRGB;
}
}
switch(type) {
case TEX_TYPE_RGBA_16_FLOAT:
case TEX_TYPE_RGB_16_FLOAT:
case TEX_TYPE_RG_16_FLOAT:
case TEX_TYPE_R_16_FLOAT:
case TEX_TYPE_RGBA_32_FLOAT:
case TEX_TYPE_RGB_32_FLOAT:
case TEX_TYPE_RG_32_FLOAT:
case TEX_TYPE_R_32_FLOAT:
case TEX_TYPE_DEPTH_16_FLOAT:
case TEX_TYPE_DEPTH_32_FLOAT:
cfg.flags.required |= GLTEX_FLOAT;
break;
default:
cfg.flags.undesirable |= GLTEX_FLOAT;
break;
}
GLTextureFormatInfo *fmt_info = glcommon_match_format(&cfg);
return fmt_info;
}
static void gl33_texture_type_query_fmtinfo(GLTextureFormatInfo *fmt_info, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result) {
result->optimal_pixmap_format = fmt_info->transfer_format.pixmap_format;
result->optimal_pixmap_origin = PIXMAP_ORIGIN_BOTTOMLEFT;
// TODO: Perhaps allow suboptimal transfer formats on non-GLES, as we did previously.
result->supplied_pixmap_format_supported = (pxfmt == result->optimal_pixmap_format);
result->supplied_pixmap_origin_supported = (pxorigin == result->optimal_pixmap_origin);
}
bool gl33_texture_type_query(TextureType type, TextureFlags flags, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result) {
GLTextureFormatInfo *fmt_info = pick_format(type, flags);
if(fmt_info == NULL) {
return false;
}
if(result) {
gl33_texture_type_query_fmtinfo(fmt_info, pxfmt, pxorigin, result);
}
return true;
}
void gl33_texture_get_size(Texture *tex, uint mipmap, uint *width, uint *height) {
if(mipmap >= tex->params.mipmaps) {
mipmap = tex->params.mipmaps - 1;
}
assert(mipmap < 32);
if(mipmap == 0) {
if(width != NULL) {
*width = tex->params.width;
}
if(height != NULL) {
*height = tex->params.height;
}
} else {
if(width != NULL) {
*width = max(1, tex->params.width / (1u << mipmap));
}
if(height != NULL) {
*height = max(1, tex->params.height / (1u << mipmap));
}
}
}
static GLenum target_from_class_and_layer(TextureClass cls, uint layer) {
GLenum target = class_to_gltarget(cls);
if(target == GL_TEXTURE_CUBE_MAP) {
static GLenum facemap[] = {
[CUBEMAP_FACE_POS_X] = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
[CUBEMAP_FACE_NEG_X] = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
// NOTE: apaprently these are swapped in OpenGL…
[CUBEMAP_FACE_POS_Y] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
[CUBEMAP_FACE_NEG_Y] = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
[CUBEMAP_FACE_POS_Z] = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
[CUBEMAP_FACE_NEG_Z] = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
static_assert(ARRAY_SIZE(facemap) == 6);
assert(layer < ARRAY_SIZE(facemap));
return facemap[layer];
} else if(target == GL_TEXTURE_2D) {
assert(layer == 0);
}
return target;
}
static void gl33_texture_set(Texture *tex, uint mipmap, uint layer, const Pixmap *image) {
assert(mipmap < tex->params.mipmaps);
assert(image != NULL);
TextureTypeQueryResult qr;
gl33_texture_type_query_fmtinfo(tex->fmt_info, image->format, image->origin, &qr);
assert(qr.supplied_pixmap_format_supported);
assert(qr.supplied_pixmap_origin_supported);
GLTextureTransferFormatInfo *xfer = &tex->fmt_info->transfer_format;
void *image_data = image->data.untyped;
GLuint prev_pbo = 0;
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
if(tex->pbo) {
prev_pbo = gl33_buffer_current(GL33_BUFFER_BINDING_PIXEL_UNPACK);
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_UNPACK, tex->pbo);
gl33_sync_buffer(GL33_BUFFER_BINDING_PIXEL_UNPACK);
glBufferData(GL_PIXEL_UNPACK_BUFFER, image->data_size, image_data, GL_STREAM_DRAW);
image_data = NULL;
}
uint width, height;
gl33_texture_get_size(tex, mipmap, &width, &height);
assert(width == image->width);
assert(height == image->height);
GLenum gl_target = target_from_class_and_layer(tex->params.class, layer);
GLenum ifmt = tex->fmt_info->internal_format;
GLenum xfmt = xfer->gl_format;
GLenum xtype = xfer->gl_type;
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
glCompressedTexSubImage2D(
gl_target,
mipmap,
0,
0,
width,
height,
ifmt,
image->data_size,
image_data
);
} else {
glTexSubImage2D(
gl_target,
mipmap,
0,
0,
width,
height,
xfmt,
xtype,
image_data
);
}
if(tex->pbo) {
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_UNPACK, prev_pbo);
}
tex->mipmaps_outdated = true;
}
static void apply_swizzle(GLenum gl_target, GLenum param, char val) {
GLenum swizzle_val, default_val;
switch(param) {
case GL_TEXTURE_SWIZZLE_R: default_val = GL_RED; break;
case GL_TEXTURE_SWIZZLE_G: default_val = GL_GREEN; break;
case GL_TEXTURE_SWIZZLE_B: default_val = GL_BLUE; break;
case GL_TEXTURE_SWIZZLE_A: default_val = GL_ALPHA; break;
default: UNREACHABLE;
}
switch(val) {
case 'r': swizzle_val = GL_RED; break;
case 'g': swizzle_val = GL_GREEN; break;
case 'b': swizzle_val = GL_BLUE; break;
case 'a': swizzle_val = GL_ALPHA; break;
case '0': swizzle_val = GL_ZERO; break;
case '1': swizzle_val = GL_ONE; break;
case 0 : swizzle_val = default_val; break;
default: UNREACHABLE;
}
if(glext.texture_swizzle) {
assert(r_supports(RFEAT_TEXTURE_SWIZZLE));
glTexParameteri(gl_target, param, swizzle_val);
} else if(default_val != swizzle_val) {
log_warn("Texture swizzle mask differs from default; not supported in this OpenGL version!");
}
}
static void apply_swizzle_mask(GLenum gl_target, SwizzleMask *mask) {
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_R, mask->r);
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_G, mask->g);
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_B, mask->b);
apply_swizzle(gl_target, GL_TEXTURE_SWIZZLE_A, mask->a);
}
Texture *gl33_texture_create(const TextureParams *params) {
auto tex = ALLOC(Texture, { .params = *params });
TextureParams *p = &tex->params;
TextureClass cls = p->class;
GLenum gl_target = class_to_gltarget(p->class);
tex->bind_target = gl_target;
uint required_layers;
switch(cls) {
case TEXTURE_CLASS_2D: required_layers = 1; break;
case TEXTURE_CLASS_CUBEMAP: required_layers = 6; break;
default: UNREACHABLE;
}
if(p->layers == 0) {
p->layers = required_layers;
}
assert(p->layers == required_layers);
assert(p->width > 0);
assert(p->height > 0);
if(cls == TEXTURE_CLASS_CUBEMAP) {
assert(p->width == p->height);
}
uint max_mipmaps = r_texture_util_max_num_miplevels(p->width, p->height);
assert(max_mipmaps > 0);
if(p->mipmaps == 0) {
if(p->mipmap_mode == TEX_MIPMAP_AUTO) {
p->mipmaps = TEX_MIPMAPS_MAX;
if(p->flags & TEX_FLAG_SRGB) {
log_fatal_if_debug("FIXME: sRGB textures may not support automatic mipmap generation; please write a fallback!");
}
} else {
p->mipmaps = 1;
}
}
if(p->mipmaps == TEX_MIPMAPS_MAX || p->mipmaps > max_mipmaps) {
p->mipmaps = max_mipmaps;
}
bool partial_mipmaps_ok = r_supports(RFEAT_PARTIAL_MIPMAPS);
if(!partial_mipmaps_ok && p->mipmap_mode == TEX_MIPMAP_MANUAL && p->mipmaps > 1 && p->mipmaps < max_mipmaps) {
log_fatal_if_debug("Partial mipmaps not supported in this OpenGL version");
}
if(p->anisotropy == 0) {
p->anisotropy = TEX_ANISOTROPY_DEFAULT;
}
glGenTextures(1, &tex->gl_handle);
snprintf(tex->debug_label, sizeof(tex->debug_label), "Texture #%i", tex->gl_handle);
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->fmt_info = pick_format(params->type, params->flags);
if(!tex->fmt_info) {
log_fatal("Failed to match GL format for texture type 0x%04x with flags 0x%04x", params->type, params->flags);
}
GLTextureTransferFormatInfo *xfer = &tex->fmt_info->transfer_format;
glTexParameteri(gl_target, GL_TEXTURE_WRAP_S, r_wrap_to_gl_wrap(p->wrap.s));
glTexParameteri(gl_target, GL_TEXTURE_WRAP_T, r_wrap_to_gl_wrap(p->wrap.t));
glTexParameteri(gl_target, GL_TEXTURE_MIN_FILTER, r_filter_to_gl_filter(p->filter.min, tex->fmt_info));
glTexParameteri(gl_target, GL_TEXTURE_MAG_FILTER, r_filter_to_gl_filter(p->filter.mag, tex->fmt_info));
apply_swizzle_mask(gl_target, &tex->params.swizzle);
if(partial_mipmaps_ok) {
glTexParameteri(gl_target, GL_TEXTURE_MAX_LEVEL, p->mipmaps - 1);
}
if(glext.texture_filter_anisotropic) {
glTexParameteri(gl_target, GL_TEXTURE_MAX_ANISOTROPY, p->anisotropy);
}
if((p->flags & TEX_FLAG_STREAM) && glext.pixel_buffer_object) {
glGenBuffers(1, &tex->pbo);
}
GLenum ifmt = tex->fmt_info->internal_format;
GLenum xfmt = xfer->gl_format;
GLenum xtype = xfer->gl_type;
if(glext.texture_storage) {
glTexStorage2D(gl_target, p->mipmaps, ifmt, p->width, p->height);
} else for(uint i = 0; i < p->mipmaps; ++i) {
uint w, h;
gl33_texture_get_size(tex, i, &w, &h);
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
// XXX: can't pre-allocate this without ARB_texture_storage or equivalent
} else if(cls == TEXTURE_CLASS_CUBEMAP) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
} else {
glTexImage2D(gl_target, i, ifmt, w, h, 0, xfmt, xtype, NULL);
}
}
return tex;
}
void gl33_texture_get_params(Texture *tex, TextureParams *params) {
memcpy(params, &tex->params, sizeof(*params));
}
void gl33_texture_set_filter(Texture *tex, TextureFilterMode fmin, TextureFilterMode fmag) {
if(tex->params.filter.min != fmin) {
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.filter.min = fmin;
glTexParameteri(tex->bind_target, GL_TEXTURE_MIN_FILTER, r_filter_to_gl_filter(fmin, tex->fmt_info));
}
if(tex->params.filter.mag != fmag) {
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.filter.mag = fmag;
glTexParameteri(tex->bind_target, GL_TEXTURE_MAG_FILTER, r_filter_to_gl_filter(fmag, tex->fmt_info));
}
}
void gl33_texture_set_wrap(Texture *tex, TextureWrapMode ws, TextureWrapMode wt) {
if(tex->params.wrap.s != ws) {
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.wrap.s = ws;
glTexParameteri(tex->bind_target, GL_TEXTURE_WRAP_S, r_wrap_to_gl_wrap(ws));
}
if(tex->params.wrap.t != wt) {
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
tex->params.wrap.t = wt;
glTexParameteri(tex->bind_target, GL_TEXTURE_WRAP_T, r_wrap_to_gl_wrap(wt));
}
}
void gl33_texture_invalidate(Texture *tex) {
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
log_debug("TODO/FIXME: invalidate not implemented for compressed textures");
return;
}
if(glext.texture_storage) {
if(glext.invalidate_subdata) {
#ifndef STATIC_GLES3
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
for(uint i = 0; i < tex->params.mipmaps; ++i) {
glInvalidateTexImage(tex->bind_target, i);
}
#else
UNREACHABLE;
#endif
} else {
log_debug("TODO/FIXME: can't invalidate immutable texture without GL_ARB_invalidate_subdata!");
}
return;
}
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
GLenum ifmt = tex->fmt_info->internal_format;
GLenum xfmt = tex->fmt_info->transfer_format.gl_format;
GLenum xtype = tex->fmt_info->transfer_format.gl_type;
for(uint i = 0; i < tex->params.mipmaps; ++i) {
uint w, h;
gl33_texture_get_size(tex, i, &w, &h );
if(tex->params.class == TEXTURE_CLASS_CUBEMAP) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, i, ifmt, w, h, 0, xfmt, xtype, NULL);
} else {
glTexImage2D(tex->bind_target, i, ifmt, w, h, 0, xfmt, xtype, NULL);
}
}
}
void gl33_texture_fill(Texture *tex, uint mipmap, uint layer, const Pixmap *image) {
assert(mipmap == 0 || tex->params.mipmap_mode != TEX_MIPMAP_AUTO);
gl33_texture_set(tex, mipmap, layer, image);
}
void gl33_texture_fill_region(Texture *tex, uint mipmap, uint layer, uint x, uint y, const Pixmap *image) {
assert(mipmap == 0 || tex->params.mipmap_mode != TEX_MIPMAP_AUTO);
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
TextureTypeQueryResult qr;
gl33_texture_type_query_fmtinfo(tex->fmt_info, image->format, image->origin, &qr);
assert(qr.supplied_pixmap_format_supported);
assert(qr.supplied_pixmap_origin_supported);
GLTextureTransferFormatInfo *xfer = &tex->fmt_info->transfer_format;
GLenum gl_target = target_from_class_and_layer(tex->params.class, layer);
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
glCompressedTexSubImage2D(
gl_target, mipmap,
x, tex->params.height - y - image->height, image->width, image->height,
tex->fmt_info->internal_format,
image->data_size,
image->data.untyped
);
} else {
glTexSubImage2D(
gl_target, mipmap,
x, tex->params.height - y - image->height, image->width, image->height,
xfer->gl_format,
xfer->gl_type,
image->data.untyped
);
}
tex->mipmaps_outdated = true;
}
void gl44_texture_clear(Texture *tex, const Color *clr) {
#ifdef STATIC_GLES3
UNREACHABLE;
#else
assert(tex->params.class == TEXTURE_CLASS_2D);
for(int i = 0; i < tex->params.mipmaps; ++i) {
glClearTexImage(tex->gl_handle, i, GL_RGBA, GL_FLOAT, &clr->r);
}
#endif
}
void gl33_texture_clear(Texture *tex, const Color *clr) {
assert(tex->params.class == TEXTURE_CLASS_2D);
// TODO: maybe find a more efficient method
Framebuffer *temp_fb = r_framebuffer_create();
r_framebuffer_attach(temp_fb, tex, 0, FRAMEBUFFER_ATTACH_COLOR0);
r_framebuffer_clear(temp_fb, BUFFER_COLOR, clr, 1);
r_framebuffer_destroy(temp_fb);
}
void gl33_texture_destroy(Texture *tex) {
gl33_texture_deleted(tex);
glDeleteTextures(1, &tex->gl_handle);
if(tex->pbo) {
glDeleteBuffers(1, &tex->pbo);
}
mem_free(tex);
}
void gl33_texture_taint(Texture *tex) {
tex->mipmaps_outdated = true;
}
void gl33_texture_prepare(Texture *tex) {
if(tex->params.mipmap_mode == TEX_MIPMAP_AUTO && tex->mipmaps_outdated) {
// log_debug("Generating mipmaps for %s (%u)", tex->debug_label, tex->gl_handle);
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
glGenerateMipmap(tex->bind_target);
tex->mipmaps_outdated = false;
}
}
const char *gl33_texture_get_debug_label(Texture *tex) {
return tex->debug_label;
}
void gl33_texture_set_debug_label(Texture *tex, const char *label) {
glcommon_set_debug_label(tex->debug_label, "Texture", GL_TEXTURE, tex->gl_handle, label);
}
bool gl33_texture_sampler_compatible(Texture *tex, UniformType sampler_type) {
assert(UNIFORM_TYPE_IS_SAMPLER(sampler_type));
switch(tex->bind_target) {
case GL_TEXTURE_2D:
return sampler_type == UNIFORM_SAMPLER_2D;
case GL_TEXTURE_CUBE_MAP:
return sampler_type == UNIFORM_SAMPLER_CUBE;
default:
UNREACHABLE;
}
}
bool gl33_texture_dump(Texture *tex, uint mipmap, uint layer, Pixmap *dst) {
#ifdef STATIC_GLES3
UNREACHABLE;
#else
if(tex->fmt_info->flags & GLTEX_COMPRESSED) {
// TODO
return false;
}
GLenum gl_target = target_from_class_and_layer(tex->params.class, layer);
gl33_texture_get_size(tex, mipmap, &dst->width, &dst->height);
dst->origin = PIXMAP_ORIGIN_BOTTOMLEFT;
dst->format = tex->fmt_info->transfer_format.pixmap_format;
dst->data.untyped = pixmap_alloc_buffer_for_copy(dst, &dst->data_size);
gl33_bind_texture(tex, 0, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
glGetTexImage(
gl_target,
mipmap,
tex->fmt_info->transfer_format.gl_format,
tex->fmt_info->transfer_format.gl_type,
dst->data.untyped
);
return true;
#endif
}
bool gl33_texture_transfer(Texture *dst, Texture *src) {
gl33_texture_deleted(dst);
glDeleteTextures(1, &dst->gl_handle);
*dst = *src;
gl33_texture_pointer_renamed(src, dst);
mem_free(src);
return true;
}