mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
A per-draw CPU profile of a real Minecraft frame (perf on the render thread, which sits at 100% of one core on both backends) said the deficit is translation overhead, not the GPU, and named where it goes. This removes the largest items it found, on both backends and in the shared frontend they both feed. The single biggest one was not translation at all: IsBackendContextCurrentOnThisThread called eglGetCurrentContext on every invocation, and glvnd answers that with a getpid() fork check - a real syscall. The predicate sits two and three deep in every draw (the deferred-release drain, the global-UBO ring availability check, and the ring allocation), so it accounted for 16.3% of the render thread. EGL is still the ground truth, but re-verifying it once per thread per frame catches an external migration at the next frame boundary rather than the next call, which recovers the same bookkeeping. Texture uploads now carry a dirty region instead of a per-level flag. Minecraft animates atlas sprites with 16x16 glTexSubImage2D calls into a 1024x512 atlas and respecifies the lightmap every frame; a per-level flag turned each of those into a full-level re-upload - about 3.6 MB a frame of texels nobody changed. MipmapStorage accumulates the written box, Espryt uploads it with UNPACK_ROW_LENGTH striding into the level shadow, and Magma stages just that box. The box is a union, not a range list: repeated writes to one level widen it and it degrades to exactly the old whole-level upload, which is the honest worst case. glBufferData(NULL) is the orphaning idiom, and the backend was answering it by uploading the stale CPU shadow - turning a rename the driver does for free into a full synchronized upload. BufferObject now records that a NULL respecify leaves the store undefined, and the upload is skipped until content is actually written. The rest are smaller and of a kind: the deferred-release queue is probed without taking its mutex, the UBO ring waits on the frame fence that frees the space it needs instead of draining the whole pipeline with glFinish at the size cap, VAO binds go through a shadow so a draw's second bind of the same object does not reach the driver, the per-draw clean-texture probe short-circuits on the content version before rebuilding shape info, glUniform drops byte-identical writes (which otherwise dirty the whole UBO for the next draw), re-binding the texture or VAO a slot already holds no longer bumps the generation counters a backend fast path is keyed on, and the texture validators stopped taking shared_ptr by value. On Magma: descriptor-set reuse keeps four entries instead of one, because draws alternating between two programs - the chunk/entity ping-pong - thrashed a single slot into a full re-allocate and re-write every draw; a DynamicDraw buffer whose contents survive two frame boundaries is promoted to resident storage instead of being re-copied into the per-frame arena forever; and sampled-read barriers name only the shader stages whose device feature is enabled, which also removes a latent VUID violation (ALL_GRAPHICS names geometry and tessellation stages a device need not have). Measured with the Minecraft rig (render distance 32, p50 fps, same machine, single sample each): vanilla 1.21.1 Espryt 10.8 -> 36.3 and Magma 31.3 -> 44.6; 26.2 snapshot Magma 114.5 -> 210.5. Fabric+Sodium moved inside noise on Magma (854 -> 766) with the native baseline itself moving 838 -> 1031 between the two sessions, so treat that cell as unresolved rather than a regression measured. Unit tests 421/421. The CTS A/B was not run: these numbers and the test suite are the whole of the evidence, and a conformance regression would not have been caught here.
5181 lines
278 KiB
C++
5181 lines
278 KiB
C++
// MobileGL - MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#include "GL_Texture.h"
|
|
#include "Config.h"
|
|
#include "MG_State/GLState/TextureState/TextureObject2D.h"
|
|
#include "MG_Util/Types.h"
|
|
#include "Validators.h"
|
|
#include "ProxyTexture.h"
|
|
|
|
#include <MG_State/GLState/Core.h>
|
|
#include <MG_Backend/BackendObjects.h>
|
|
#include <MG_Util/Metrics/TextureMetrics.h>
|
|
#include <MG_State/GLState/ErrorState/Error.h>
|
|
#include <MG_Util/Texture/PixelStoreProcessor.h>
|
|
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
|
#include <MG_Util/Classifiers/TextureEnumClassifier.h>
|
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToMG/TextureEnumConverter.h>
|
|
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
|
|
#include <MG_Impl/GLImpl/Framebuffer/Validators.h>
|
|
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
|
#include <MG_State/GLState/TextureState/TextureObjectBuffer.h>
|
|
|
|
namespace MobileGL::MG_Impl::GLImpl {
|
|
static SharedPtr<MG_State::GLState::ITextureObject> nullTextureObject;
|
|
static UnorderedMap<Uint, Bool> g_autoGenerateMipmapByTextureId;
|
|
|
|
Bool GetTexParameteriv_State(GLenum target, GLenum pname, GLint* params);
|
|
|
|
namespace {
|
|
void SetTextureBorderColorFromFloats(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const GLfloat* params) {
|
|
textureObject->SetBorderColor(FloatVec4(params[0], params[1], params[2], params[3]));
|
|
}
|
|
|
|
void SetTextureBorderColorFromInts(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const GLint* params) {
|
|
constexpr Float kSignedIntToFloat = 1.0f / 2147483647.0f;
|
|
textureObject->SetBorderColor(FloatVec4(static_cast<Float>(params[0]) * kSignedIntToFloat,
|
|
static_cast<Float>(params[1]) * kSignedIntToFloat,
|
|
static_cast<Float>(params[2]) * kSignedIntToFloat,
|
|
static_cast<Float>(params[3]) * kSignedIntToFloat));
|
|
}
|
|
|
|
void SetTextureBorderColorFromIntegerInts(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const GLint* params) {
|
|
textureObject->SetBorderColorI(IntVec4(params[0], params[1], params[2], params[3]));
|
|
}
|
|
|
|
void SetTextureBorderColorFromUnsignedInts(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const GLuint* params) {
|
|
textureObject->SetBorderColorUI(UintVec4(params[0], params[1], params[2], params[3]));
|
|
}
|
|
|
|
Bool SetTextureSwizzleParamsFromInts(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const GLint* params, const char* caller) {
|
|
Vec4<TextureSwizzleParam> swizzleParams;
|
|
for (int i = 0; i < 4; ++i) {
|
|
swizzleParams[i] = MG_Util::ConvertGLEnumToTextureSwizzleParam(params[i]);
|
|
if (TextureSwizzleParam::Unknown == swizzleParams[i]) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "`params` is not valid."));
|
|
return false;
|
|
}
|
|
}
|
|
textureObject->SetSwizzleParamRGBA(swizzleParams);
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateMaxAnisotropy(Float maxAnisotropy, const char* caller) {
|
|
if (maxAnisotropy >= 1.0f) return true;
|
|
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1.0."));
|
|
return false;
|
|
}
|
|
|
|
template <typename Fn>
|
|
void WithTemporarilyBoundNamedTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
Fn&& fn) {
|
|
if (!textureObject) return;
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureObject->GetTarget());
|
|
const auto previousBinding = bindingSlot.GetBoundObject();
|
|
bindingSlot.Bind(textureObject);
|
|
fn(MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget()));
|
|
bindingSlot.Bind(previousBinding);
|
|
}
|
|
|
|
SizeT ComputeTextureStorageByteSize(TextureInternalFormat textureInternalFormat, GLsizei width, GLsizei height,
|
|
GLsizei depth) {
|
|
GLenum realInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat);
|
|
GLenum realFormat = GL_RGBA;
|
|
GLenum realType = GL_UNSIGNED_BYTE;
|
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
|
realInternalFormat, PixelFormatNormalizeOptionBit::None, &realInternalFormat, &realFormat, &realType);
|
|
return static_cast<SizeT>(width) * static_cast<SizeT>(height) * static_cast<SizeT>(depth) *
|
|
MG_Util::GetInternalBytesPerPixel(textureInternalFormat,
|
|
MG_Util::ConvertGLEnumToTexturePixelDataType(realType));
|
|
}
|
|
|
|
Bool IsSizedTextureStorageInternalFormat(TextureInternalFormat textureInternalFormat) {
|
|
switch (textureInternalFormat) {
|
|
case TextureInternalFormat::Red:
|
|
case TextureInternalFormat::RG:
|
|
case TextureInternalFormat::RGB:
|
|
case TextureInternalFormat::RGBA:
|
|
case TextureInternalFormat::DepthComponent:
|
|
case TextureInternalFormat::DepthStencil:
|
|
case TextureInternalFormat::Unknown:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Bool ValidateTextureStorageInternalFormat(TextureInternalFormat textureInternalFormat, const char* caller) {
|
|
// TODO: Replace this sized-format filter with the full ARB_texture_storage legal-format table.
|
|
if (!IsSizedTextureStorageInternalFormat(textureInternalFormat)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"TexStorage requires a sized internal format."));
|
|
return false;
|
|
}
|
|
return TextureImpl::ValidateTextureInternalFormat(textureInternalFormat);
|
|
}
|
|
|
|
Bool ValidateTextureMutable(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const char* caller) {
|
|
if (!textureObject || !textureObject->IsImmutable()) return true;
|
|
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Immutable texture storage cannot be redefined."));
|
|
return false;
|
|
}
|
|
|
|
GLint GetTextureComponentType(TextureInternalFormat textureInternalFormat, GLint size, Bool depthComponent,
|
|
Bool stencilComponent) {
|
|
if (size <= 0) return GL_NONE;
|
|
if (stencilComponent) return GL_UNSIGNED_INT;
|
|
if (depthComponent) {
|
|
return (textureInternalFormat == TextureInternalFormat::DepthComponent32F ||
|
|
textureInternalFormat == TextureInternalFormat::Depth32FStencil8)
|
|
? GL_FLOAT
|
|
: GL_UNSIGNED_NORMALIZED;
|
|
}
|
|
switch (textureInternalFormat) {
|
|
case TextureInternalFormat::R8I:
|
|
case TextureInternalFormat::R16I:
|
|
case TextureInternalFormat::R32I:
|
|
case TextureInternalFormat::RG8I:
|
|
case TextureInternalFormat::RG16I:
|
|
case TextureInternalFormat::RG32I:
|
|
case TextureInternalFormat::RGB8I:
|
|
case TextureInternalFormat::RGB16I:
|
|
case TextureInternalFormat::RGB32I:
|
|
case TextureInternalFormat::RGBA8I:
|
|
case TextureInternalFormat::RGBA16I:
|
|
case TextureInternalFormat::RGBA32I:
|
|
return GL_INT;
|
|
case TextureInternalFormat::R8UI:
|
|
case TextureInternalFormat::R16UI:
|
|
case TextureInternalFormat::R32UI:
|
|
case TextureInternalFormat::RG8UI:
|
|
case TextureInternalFormat::RG16UI:
|
|
case TextureInternalFormat::RG32UI:
|
|
case TextureInternalFormat::RGB8UI:
|
|
case TextureInternalFormat::RGB16UI:
|
|
case TextureInternalFormat::RGB32UI:
|
|
case TextureInternalFormat::RGBA8UI:
|
|
case TextureInternalFormat::RGBA16UI:
|
|
case TextureInternalFormat::RGBA32UI:
|
|
case TextureInternalFormat::RGB10A2UI:
|
|
return GL_UNSIGNED_INT;
|
|
case TextureInternalFormat::R16F:
|
|
case TextureInternalFormat::RG16F:
|
|
case TextureInternalFormat::RGB16F:
|
|
case TextureInternalFormat::RGBA16F:
|
|
case TextureInternalFormat::R32F:
|
|
case TextureInternalFormat::RG32F:
|
|
case TextureInternalFormat::RGB32F:
|
|
case TextureInternalFormat::RGBA32F:
|
|
case TextureInternalFormat::R11FG11FB10F:
|
|
case TextureInternalFormat::RGB9E5:
|
|
return GL_FLOAT;
|
|
case TextureInternalFormat::R8Snorm:
|
|
case TextureInternalFormat::R16Snorm:
|
|
case TextureInternalFormat::RG8Snorm:
|
|
case TextureInternalFormat::RG16Snorm:
|
|
case TextureInternalFormat::RGB8Snorm:
|
|
case TextureInternalFormat::RGB16Snorm:
|
|
case TextureInternalFormat::RGBA8Snorm:
|
|
case TextureInternalFormat::RGBA16Snorm:
|
|
return GL_SIGNED_NORMALIZED;
|
|
default:
|
|
return GL_UNSIGNED_NORMALIZED;
|
|
}
|
|
}
|
|
|
|
// TextureInternalFormat has no compressed enumerator and CompressedTexImage* rejects every
|
|
// compressed format up front, so no texture image MobileGL holds can be compressed. Written
|
|
// as a predicate rather than a literal false so both level-parameter getters stay in step
|
|
// once compressed formats do land.
|
|
// GL 4.6 core 8.11 asks "is *this level* stored compressed", not "is the texture's internal
|
|
// format a compressed one", and here the two genuinely differ: a compressed internalformat
|
|
// handed to glTexImage2D resolves to the uncompressed storage that backs it (see
|
|
// ConvertGLEnumToTextureInternalFormat), so the texture's format enum can never answer yes.
|
|
// The only levels stored compressed are the ones glCompressedTexImage* shadowed verbatim,
|
|
// which is exactly what the per-level compressed format records.
|
|
//
|
|
// No level-count guard on purpose: TextureObject2DCube::GetMipmapLevelCount() reports face
|
|
// zero's chain only, so a count check would answer GL_NONE for a compressed image on any
|
|
// other face - precisely the per-face independence the storage layer provides. MipmapStorage's
|
|
// own getters already bounds-check per target and return GL_NONE for an unallocated level.
|
|
GLenum GetCompressedLevelFormat(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
TextureUploadTarget uploadTarget, GLint level) {
|
|
if (!textureObject || level < 0) return GL_NONE;
|
|
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
|
if (!textureMipmapObject) return GL_NONE;
|
|
return textureMipmapObject->GetMipmapCompressedFormat(uploadTarget, static_cast<Uint>(level));
|
|
}
|
|
|
|
GLint GetTextureLevelComponentParameter(TextureInternalFormat textureInternalFormat, GLenum pname) {
|
|
const ComponentSizes componentSizes = MG_Util::GetComponentSizesForInternalFormat(textureInternalFormat);
|
|
switch (pname) {
|
|
case GL_TEXTURE_RED_SIZE:
|
|
return componentSizes.Red;
|
|
case GL_TEXTURE_GREEN_SIZE:
|
|
return componentSizes.Green;
|
|
case GL_TEXTURE_BLUE_SIZE:
|
|
return componentSizes.Blue;
|
|
case GL_TEXTURE_ALPHA_SIZE:
|
|
return componentSizes.Alpha;
|
|
case GL_TEXTURE_DEPTH_SIZE:
|
|
return componentSizes.Depth;
|
|
case GL_TEXTURE_STENCIL_SIZE:
|
|
return componentSizes.Stencil;
|
|
case GL_TEXTURE_RED_TYPE:
|
|
return GetTextureComponentType(textureInternalFormat, componentSizes.Red, false, false);
|
|
case GL_TEXTURE_GREEN_TYPE:
|
|
return GetTextureComponentType(textureInternalFormat, componentSizes.Green, false, false);
|
|
case GL_TEXTURE_BLUE_TYPE:
|
|
return GetTextureComponentType(textureInternalFormat, componentSizes.Blue, false, false);
|
|
case GL_TEXTURE_ALPHA_TYPE:
|
|
return GetTextureComponentType(textureInternalFormat, componentSizes.Alpha, false, false);
|
|
case GL_TEXTURE_DEPTH_TYPE:
|
|
return GetTextureComponentType(textureInternalFormat, componentSizes.Depth, true, false);
|
|
default:
|
|
MOBILEGL_ASSERT(false, "Invalid texture level component pname: %d", pname);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
Bool IsValidImageTextureFormat(GLenum format) {
|
|
switch (format) {
|
|
case GL_RGBA32F:
|
|
case GL_RGBA16F:
|
|
case GL_RG32F:
|
|
case GL_RG16F:
|
|
case GL_R11F_G11F_B10F:
|
|
case GL_R32F:
|
|
case GL_R16F:
|
|
case GL_RGBA32UI:
|
|
case GL_RGBA16UI:
|
|
case GL_RGB10_A2UI:
|
|
case GL_RGBA8UI:
|
|
case GL_RG32UI:
|
|
case GL_RG16UI:
|
|
case GL_RG8UI:
|
|
case GL_R32UI:
|
|
case GL_R16UI:
|
|
case GL_R8UI:
|
|
case GL_RGBA32I:
|
|
case GL_RGBA16I:
|
|
case GL_RGBA8I:
|
|
case GL_RG32I:
|
|
case GL_RG16I:
|
|
case GL_RG8I:
|
|
case GL_R32I:
|
|
case GL_R16I:
|
|
case GL_R8I:
|
|
case GL_RGBA16:
|
|
case GL_RGB10_A2:
|
|
case GL_RGBA8:
|
|
case GL_RG16:
|
|
case GL_RG8:
|
|
case GL_R16:
|
|
case GL_R8:
|
|
case GL_RGBA16_SNORM:
|
|
case GL_RGBA8_SNORM:
|
|
case GL_RG16_SNORM:
|
|
case GL_RG8_SNORM:
|
|
case GL_R16_SNORM:
|
|
case GL_R8_SNORM:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GLuint GetAdvertisedImageUnitCount() {
|
|
return static_cast<GLuint>(std::min<GLint>(
|
|
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxImageUnits,
|
|
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS));
|
|
}
|
|
|
|
// Array targets store their layer count in z; layers never participate in mip
|
|
// reduction (GL 3.3 §3.8.14), only true 3D textures halve their depth per level.
|
|
Bool DepthParticipatesInMipmapping(TextureTarget target) {
|
|
return target == TextureTarget::Texture3D;
|
|
}
|
|
|
|
// Which targets each glTextureStorage*D accepts (GL 4.6 core 8.19). A texture whose target
|
|
// belongs to a different one of the three is the wrong object, not a bad argument, so it is
|
|
// INVALID_OPERATION.
|
|
Bool IsTextureStorageTargetForDimension(TextureTarget target, int dimension) {
|
|
switch (dimension) {
|
|
case 1:
|
|
return target == TextureTarget::Texture1D;
|
|
case 2:
|
|
return target == TextureTarget::Texture2D || target == TextureTarget::Texture1DArray ||
|
|
target == TextureTarget::TextureRectangle || target == TextureTarget::TextureCubeMap;
|
|
case 3:
|
|
return target == TextureTarget::Texture3D || target == TextureTarget::Texture2DArray ||
|
|
target == TextureTarget::TextureCubeMapArray;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The longest mip chain the level-0 size admits. A 1D array keeps its layer count in
|
|
// height, so unlike a 2D texture its height takes no part in the reduction.
|
|
Uint ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize, Bool depthMips);
|
|
|
|
Uint MaxTextureStorageLevels(TextureTarget target, GLsizei width, GLsizei height, GLsizei depth) {
|
|
const Int mipHeight = (target == TextureTarget::Texture1DArray) ? 1 : std::max<Int>(height, 1);
|
|
return ComputeFullMipmapLevelCount({std::max<Int>(width, 1), mipHeight, std::max<Int>(depth, 1)},
|
|
DepthParticipatesInMipmapping(target));
|
|
}
|
|
|
|
Uint ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize, Bool depthMips) {
|
|
Int maxDimension = std::max<Int>(
|
|
baseTexelSize.x(),
|
|
std::max<Int>(baseTexelSize.y(), depthMips ? std::max<Int>(baseTexelSize.z(), 1) : 1));
|
|
Uint mipLevelCount = 1;
|
|
while (maxDimension > 1) {
|
|
maxDimension = std::max<Int>(maxDimension / 2, 1);
|
|
++mipLevelCount;
|
|
}
|
|
return mipLevelCount;
|
|
}
|
|
|
|
IntVec3 ComputeMipmapTexelSize(const IntVec3& baseTexelSize, Uint relativeLevel, Bool depthMips) {
|
|
return {
|
|
std::max<Int>(baseTexelSize.x() >> static_cast<Int>(relativeLevel), 1),
|
|
std::max<Int>(baseTexelSize.y() >> static_cast<Int>(relativeLevel), 1),
|
|
depthMips ? std::max<Int>(baseTexelSize.z() >> static_cast<Int>(relativeLevel), 1)
|
|
: std::max<Int>(baseTexelSize.z(), 1),
|
|
};
|
|
}
|
|
|
|
Bool EnsureGeneratedMipmapStorageAllocated(
|
|
MG_State::GLState::TextureObjectMipmap& texture,
|
|
TextureUploadTarget uploadTarget) {
|
|
const Uint existingLevelCount = texture.GetMipmapLevelCount();
|
|
if (existingLevelCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
const IntVec3 baseTexelSize = texture.GetMipmapTexelSize(uploadTarget, 0);
|
|
const SizeT baseByteSize = texture.GetMipmapByteSize(uploadTarget, 0);
|
|
const SizeT baseTexelCount = static_cast<SizeT>(baseTexelSize.x()) *
|
|
static_cast<SizeT>(baseTexelSize.y()) *
|
|
static_cast<SizeT>(baseTexelSize.z());
|
|
if (baseTexelSize.x() <= 0 || baseTexelSize.y() <= 0 || baseTexelSize.z() <= 0 ||
|
|
baseByteSize == 0 || baseTexelCount == 0 || (baseByteSize % baseTexelCount) != 0) {
|
|
return false;
|
|
}
|
|
|
|
const SizeT bytesPerTexel = baseByteSize / baseTexelCount;
|
|
const Bool depthMips = DepthParticipatesInMipmapping(texture.GetTarget());
|
|
const Uint requiredLevelCount = ComputeFullMipmapLevelCount(baseTexelSize, depthMips);
|
|
for (Uint level = 1; level < requiredLevelCount; ++level) {
|
|
const IntVec3 levelTexelSize = ComputeMipmapTexelSize(baseTexelSize, level, depthMips);
|
|
const SizeT levelByteSize = bytesPerTexel * static_cast<SizeT>(levelTexelSize.x()) *
|
|
static_cast<SizeT>(levelTexelSize.y()) *
|
|
static_cast<SizeT>(levelTexelSize.z());
|
|
texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize});
|
|
texture.MarkStorageDirty(uploadTarget, level, false);
|
|
}
|
|
// glGenerateMipmap defines exactly levels 0..requiredLevelCount-1. AllocateStorage only
|
|
// grows, so a previously longer chain (a bigger base image before respecification) would
|
|
// otherwise keep a tail of stale levels here and read as incomplete.
|
|
texture.TruncateMipmapLevels(uploadTarget, requiredLevelCount);
|
|
// Mip generation grows/regenerates the level set on the GPU without marking any CPU
|
|
// level dirty (MarkStorageDirty(...,false) above). Bump the content version so the
|
|
// backend re-syncs: a cached sampled VkImageView built for the pre-generate level
|
|
// range would otherwise stay stale and clamp LOD>0 sampling to mip 0.
|
|
texture.BumpContentVersion();
|
|
return true;
|
|
}
|
|
|
|
void EnsureGeneratedMipmapStorageAllocated(MG_State::GLState::TextureObjectMipmap& texture) {
|
|
for (const TextureUploadTarget uploadTarget : texture.GetUploadTargets()) {
|
|
EnsureGeneratedMipmapStorageAllocated(texture, uploadTarget);
|
|
}
|
|
}
|
|
|
|
Bool IsMultisampleTextureTarget(TextureTarget target) {
|
|
return target == TextureTarget::Texture2DMultisample ||
|
|
target == TextureTarget::Texture2DMultisampleArray;
|
|
}
|
|
|
|
Int GetMaxSupportedTextureSamples(TextureInternalFormat textureInternalFormat) {
|
|
if (MG_Backend::pActiveBackendObject == nullptr) {
|
|
return std::numeric_limits<Int>::max();
|
|
}
|
|
|
|
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
|
if (MG_Util::IsDepthFormatInternalFormat(textureInternalFormat) ||
|
|
MG_Util::IsStencilFormatInternalFormat(textureInternalFormat)) {
|
|
return std::max(dynamicParameters.MaxDepthTextureSamples, 1);
|
|
}
|
|
|
|
GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat);
|
|
GLenum normalizedFormat = GL_RGBA;
|
|
GLenum normalizedType = GL_UNSIGNED_BYTE;
|
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
|
normalizedInternalFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat,
|
|
&normalizedFormat, &normalizedType);
|
|
const Bool isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER ||
|
|
normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER;
|
|
return std::max(isIntegerFormat ? dynamicParameters.MaxIntegerSamples
|
|
: dynamicParameters.MaxColorTextureSamples,
|
|
1);
|
|
}
|
|
|
|
Bool ValidateTextureMultisampleStorage(TextureTarget textureTarget, GLsizei samples, GLsizei width,
|
|
GLsizei height, GLsizei depth, TextureInternalFormat textureInternalFormat,
|
|
const char* caller) {
|
|
if (!IsMultisampleTextureTarget(textureTarget)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Target is not a multisample texture target."));
|
|
return false;
|
|
}
|
|
if (samples <= 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Sample count must be positive."));
|
|
return false;
|
|
}
|
|
if (width < 0 || height < 0 || depth < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture size must be non-negative."));
|
|
return false;
|
|
}
|
|
if (textureTarget == TextureTarget::Texture2DMultisample && depth != 1) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"2D multisample textures must use depth 1."));
|
|
return false;
|
|
}
|
|
// Zero layers is NOT an error for multisample arrays: depth == 0 (like width/height
|
|
// == 0) deallocates the image - GL 4.5 8.8 only raises INVALID_VALUE for negative
|
|
// dimensions, and GL CTS's per-case state reset (gluStateReset) clears the default
|
|
// GL_TEXTURE_2D_MULTISAMPLE_ARRAY texture with glTexImage3DMultisample(..., 0, 0, 0).
|
|
|
|
const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat);
|
|
if (samples > maxSamples) {
|
|
// GL specifies INVALID_OPERATION - not INVALID_VALUE - when the sample count
|
|
// exceeds what the format supports, and the native Adreno driver agrees.
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("Sample count {} exceeds the supported maximum {} for this texture format.",
|
|
samples, maxSamples)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AllocateMultisampleTextureStorage(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
TextureUploadTarget textureUploadTarget,
|
|
TextureInternalFormat textureInternalFormat, GLsizei samples,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
GLboolean fixedsamplelocations) {
|
|
MOBILEGL_ASSERT(textureObject != nullptr, "AllocateMultisampleTextureStorage requires a texture object");
|
|
MOBILEGL_ASSERT(textureObject->GetStorageType() == TextureStorageType::Mipmap,
|
|
"AllocateMultisampleTextureStorage requires mipmap-backed storage");
|
|
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
textureObject->SetSamples(samples);
|
|
textureObject->SetFixedSampleLocations(fixedsamplelocations == GL_TRUE);
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, 0, {{width, height, depth}, 0});
|
|
// Multisample textures are single-level by definition, so a name that previously held a
|
|
// mip chain must not keep its tail now that AllocateStorage only grows.
|
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, 1);
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, 0, false);
|
|
}
|
|
|
|
// Redefining level 0 of a texture that already had a base image drops the rest of the chain,
|
|
// which is exactly what AllocateLevel used to do implicitly for every level. Keeping that
|
|
// behaviour for level 0 - and only for level 0 - is what makes the grow-only change safe:
|
|
// any level-0 respecification leaves the chain in precisely the state it would have had
|
|
// before, while an upload to level N no longer destroys the levels beneath it.
|
|
//
|
|
// Why it has to be *every* level-0 respecification and not just a size change: Minecraft's
|
|
// Mipmap Levels setting rebuilds the block atlas at the SAME dimensions with a different
|
|
// level count. A size-only test would leave the old tail in place, and because Mojang
|
|
// terminates its chains with a 0x0 level the result is the zero-then-nonzero pattern that
|
|
// IsComplete() rejects (TextureObject.cpp) - whereupon DirectGLES skips syncing the texture
|
|
// entirely (Managers.cpp) and the atlas samples black.
|
|
//
|
|
// The "already has a base image" test is what lets the fix work at all: a level that was
|
|
// never written reads back as {0,0,0}, so building a chain top-down - upload level N first,
|
|
// then level 0 - must not discard the levels just uploaded. That ordering is what
|
|
// KHR-GL33.texture_repeat_mode does.
|
|
// Scoped to the respecified upload target only, which is what AllocateLevel already did.
|
|
// Cube maps keep six independent chains while reporting a single level count (face +X), so
|
|
// respecifying a face other than +X can leave the count longer than that face - but that
|
|
// asymmetry predates this change and widening the truncation to all six faces would destroy
|
|
// mip data for faces the application never touched. Left alone deliberately.
|
|
void DiscardMipmapChainOnBaseRespecification(MG_State::GLState::TextureObjectMipmap* texture,
|
|
TextureUploadTarget uploadTarget, Uint level) {
|
|
if (level != 0) return;
|
|
|
|
const IntVec3 existingBaseSize = texture->GetMipmapTexelSize(uploadTarget, 0);
|
|
const Bool hasExistingBaseImage =
|
|
existingBaseSize.x() > 0 && existingBaseSize.y() > 0 && existingBaseSize.z() > 0;
|
|
if (!hasExistingBaseImage) return;
|
|
|
|
texture->TruncateMipmapLevels(uploadTarget, 1);
|
|
}
|
|
|
|
// The compressed internalformat is not one this stack can store (see
|
|
// MG_Util::GetCompressedFormatInfo for the accepted set: the RGTC/BPTC/ETC2-EAC formats core
|
|
// GL requires). GL_INVALID_ENUM is the specified error for an unsupported compressed format -
|
|
// unlike THROW_UNIMPL_EXCEPTION, which unwinds a C++ exception through the C GL ABI and takes
|
|
// the process down. Still the only outcome for the 1D/3D and sub-image entry points, which
|
|
// have no compressed upload path yet.
|
|
void RecordUnsupportedCompressedFormat(const char* caller) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Compressed texture formats are not supported."));
|
|
}
|
|
} // namespace
|
|
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByName(GLuint texture, const char* caller) {
|
|
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
|
if (!textureObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
std::format("Texture object {} does not exist.", texture)));
|
|
return nullTextureObject;
|
|
}
|
|
return textureObject;
|
|
}
|
|
|
|
namespace {
|
|
void RecordClearTextureError(const char* caller, ErrorCode code, const String& message) {
|
|
MG_State::pGLContext->RecordError(
|
|
code, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, message));
|
|
}
|
|
|
|
SharedPtr<MG_State::GLState::TextureObjectMipmap> GetClearTextureObject(GLuint texture, GLint level,
|
|
const char* caller) {
|
|
if (texture == 0) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
|
"Clear texture operations require a non-zero texture name.");
|
|
return nullptr;
|
|
}
|
|
|
|
auto textureObject = GetTextureObjectByName(texture, caller);
|
|
if (!textureObject) return nullptr;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
|
"Buffer textures cannot be cleared with glClearTexImage.");
|
|
return nullptr;
|
|
}
|
|
|
|
auto mipmapTexture = std::static_pointer_cast<MG_State::GLState::TextureObjectMipmap>(textureObject);
|
|
if (level < 0) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidValue,
|
|
std::format("Texture level {} is negative.", level));
|
|
return nullptr;
|
|
}
|
|
// ARB_clear_texture: clearing an image that was never defined by TexImage*/
|
|
// TexStorage* is INVALID_OPERATION, not INVALID_VALUE.
|
|
if (static_cast<Uint>(level) >= mipmapTexture->GetMipmapLevelCount()) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
|
std::format("Texture level {} is not defined.", level));
|
|
return nullptr;
|
|
}
|
|
return mipmapTexture;
|
|
}
|
|
|
|
Bool BuildClearPixel(const SharedPtr<MG_State::GLState::TextureObjectMipmap>& textureObject,
|
|
GLenum format, GLenum type, const void* data, Vector<Uint8>& clearPixel) {
|
|
const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
const TexturePixelDataType inputType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
if (!TextureImpl::ValidateTextureInputFormat(inputFormat) ||
|
|
!TextureImpl::ValidateTexturePixelDataType(inputType) ||
|
|
!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(
|
|
inputFormat, textureObject->GetFormat(), inputType)) {
|
|
return false;
|
|
}
|
|
|
|
clearPixel.clear();
|
|
if (data == nullptr) {
|
|
// ARB_clear_texture defines a null clear value as all zeroes. Keeping the
|
|
// pattern empty lets the region writer use a fast memset path.
|
|
return true;
|
|
}
|
|
|
|
PixelStoreParameters clearPixelStore{};
|
|
clearPixelStore.Alignment = 1;
|
|
SizeT clearPixelSize = 0;
|
|
void* converted = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
data, clearPixelStore, textureObject->GetFormat(), inputFormat, inputType,
|
|
{1, 1, 1}, false, clearPixelSize);
|
|
if (!converted || clearPixelSize == 0) {
|
|
if (converted) free(converted);
|
|
return false;
|
|
}
|
|
|
|
clearPixel.resize(clearPixelSize);
|
|
Memcpy(clearPixel.data(), converted, clearPixelSize);
|
|
free(converted);
|
|
return true;
|
|
}
|
|
|
|
// Writes the clear into the CPU shadow and marks the whole level dirty, exactly like
|
|
// TexSubImage*_State does. Shared limitation of the level-granular shadow sync: the
|
|
// shadow does not reflect GPU-side writes (FBO rendering, imageStore), so a PARTIAL
|
|
// clear of a GPU-written level re-uploads stale shadow bytes outside the region on
|
|
// the next sync. Full-level clears (glClearTexImage, or a sub-clear covering the
|
|
// level) rewrite the entire shadow and are always correct.
|
|
Bool ClearMipmapRegion(const SharedPtr<MG_State::GLState::TextureObjectMipmap>& textureObject,
|
|
TextureUploadTarget uploadTarget, GLint level,
|
|
GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLsizei width, GLsizei height, GLsizei depth,
|
|
const Vector<Uint8>& clearPixel, const char* caller) {
|
|
const IntVec3 texelSize = textureObject->GetMipmapTexelSize(uploadTarget, static_cast<Uint>(level));
|
|
if (texelSize.x() <= 0 || texelSize.y() <= 0 || texelSize.z() <= 0) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
|
"The requested texture level has no storage.");
|
|
return false;
|
|
}
|
|
if (xoffset < 0 || yoffset < 0 || zoffset < 0 ||
|
|
width < 0 || height < 0 || depth < 0 ||
|
|
width > texelSize.x() - xoffset ||
|
|
height > texelSize.y() - yoffset ||
|
|
depth > texelSize.z() - zoffset) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidValue,
|
|
"The clear region lies outside the requested texture level.");
|
|
return false;
|
|
}
|
|
if (width == 0 || height == 0 || depth == 0) return true;
|
|
|
|
const SizeT texelCount = static_cast<SizeT>(texelSize.x()) *
|
|
static_cast<SizeT>(texelSize.y()) *
|
|
static_cast<SizeT>(texelSize.z());
|
|
const SizeT byteSize = textureObject->GetMipmapByteSize(uploadTarget, static_cast<Uint>(level));
|
|
if (byteSize == 0 || byteSize % texelCount != 0) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
|
"The requested texture storage cannot be cleared.");
|
|
return false;
|
|
}
|
|
|
|
const SizeT bytesPerTexel = byteSize / texelCount;
|
|
if (!clearPixel.empty() && clearPixel.size() != bytesPerTexel) {
|
|
RecordClearTextureError(
|
|
caller, ErrorCode::InvalidOperation,
|
|
std::format("Converted clear value is {} bytes, but the texture stores {} bytes per texel.",
|
|
clearPixel.size(), bytesPerTexel));
|
|
return false;
|
|
}
|
|
|
|
auto* destination = static_cast<Uint8*>(
|
|
textureObject->MapMipmapData(uploadTarget, static_cast<Uint>(level)));
|
|
if (!destination) {
|
|
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
|
"The requested texture level could not be mapped.");
|
|
return false;
|
|
}
|
|
|
|
const SizeT fullRowBytes = static_cast<SizeT>(texelSize.x()) * bytesPerTexel;
|
|
const SizeT fullSliceBytes = static_cast<SizeT>(texelSize.y()) * fullRowBytes;
|
|
const SizeT clearRowBytes = static_cast<SizeT>(width) * bytesPerTexel;
|
|
Uint8* firstClearRow = nullptr;
|
|
|
|
for (GLsizei z = 0; z < depth; ++z) {
|
|
for (GLsizei y = 0; y < height; ++y) {
|
|
Uint8* row = destination +
|
|
static_cast<SizeT>(zoffset + z) * fullSliceBytes +
|
|
static_cast<SizeT>(yoffset + y) * fullRowBytes +
|
|
static_cast<SizeT>(xoffset) * bytesPerTexel;
|
|
if (firstClearRow) {
|
|
Memcpy(row, firstClearRow, clearRowBytes);
|
|
continue;
|
|
}
|
|
|
|
firstClearRow = row;
|
|
if (clearPixel.empty()) {
|
|
Memset(row, 0, clearRowBytes);
|
|
continue;
|
|
}
|
|
|
|
Memcpy(row, clearPixel.data(), bytesPerTexel);
|
|
SizeT filled = bytesPerTexel;
|
|
while (filled < clearRowBytes) {
|
|
const SizeT copySize = std::min(filled, clearRowBytes - filled);
|
|
Memcpy(row + filled, row, copySize);
|
|
filled += copySize;
|
|
}
|
|
}
|
|
}
|
|
|
|
textureObject->MarkStorageDirty(uploadTarget, static_cast<Uint>(level), true);
|
|
return true;
|
|
}
|
|
// GL 4.6 core 8.6: CopyTexSubImage* is not affected by pixel-store state or by a bound
|
|
// pack buffer, but the backend readback this borrows honours both. Neutralise them for the
|
|
// duration of the read and put them back afterwards.
|
|
class ScopedNeutralPackState {
|
|
public:
|
|
ScopedNeutralPackState() {
|
|
for (SizeT i = 0; i < kParams.size(); ++i) {
|
|
m_saved[i] = MG_State::pGLContext->GetPixelStoreParam(kParams[i]);
|
|
MG_State::pGLContext->SetPixelStoreParam(kParams[i], i == 0 ? 1 : 0);
|
|
}
|
|
auto& slot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack);
|
|
m_savedPackBuffer = slot.GetBoundObject();
|
|
slot.Bind(nullptr);
|
|
}
|
|
~ScopedNeutralPackState() {
|
|
for (SizeT i = 0; i < kParams.size(); ++i) {
|
|
MG_State::pGLContext->SetPixelStoreParam(kParams[i], m_saved[i]);
|
|
}
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).Bind(m_savedPackBuffer);
|
|
}
|
|
|
|
private:
|
|
// PackAlignment must be first: it is the one that resets to 1 rather than 0.
|
|
static constexpr Array<PixelStoreParam, 8> kParams{
|
|
PixelStoreParam::PackAlignment, PixelStoreParam::PackRowLength,
|
|
PixelStoreParam::PackImageHeight, PixelStoreParam::PackSkipRows,
|
|
PixelStoreParam::PackSkipPixels, PixelStoreParam::PackSkipImages,
|
|
PixelStoreParam::PackSwapBytes, PixelStoreParam::PackLSBFirst};
|
|
Array<Int, 8> m_saved{};
|
|
SharedPtr<MG_State::GLState::BufferObject> m_savedPackBuffer;
|
|
};
|
|
|
|
// The copy half of glCopyTexSubImage*: read the region out of the read framebuffer and write
|
|
// it into the destination level's CPU storage. Done in the frontend because that storage is
|
|
// where a texture's contents actually live - the backends sync from it - so this needs no
|
|
// 1D or 3D blit, which neither backend has.
|
|
Bool CopyReadFramebufferIntoMipmapRegion(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
TextureUploadTarget uploadTarget, GLint level, GLint xoffset,
|
|
GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width,
|
|
GLsizei height, const char* caller) {
|
|
if (width <= 0 || height <= 0) return true;
|
|
auto* mipmapTexture = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
|
if (!mipmapTexture) return false;
|
|
|
|
const auto texelSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, static_cast<Uint>(level));
|
|
const SizeT texelCount = static_cast<SizeT>(texelSize.x()) * static_cast<SizeT>(texelSize.y()) *
|
|
static_cast<SizeT>(texelSize.z());
|
|
const SizeT byteSize = mipmapTexture->GetMipmapByteSize(uploadTarget, static_cast<Uint>(level));
|
|
if (texelCount == 0 || byteSize == 0 || byteSize % texelCount != 0) return false;
|
|
const SizeT bytesPerTexel = byteSize / texelCount;
|
|
|
|
// Read in the destination's own canonical client layout, so the bytes land in storage
|
|
// without a second conversion.
|
|
const GLenum glInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureObject->GetFormat());
|
|
GLenum realInternalFormat = glInternalFormat;
|
|
GLenum format = GL_RGBA;
|
|
GLenum type = GL_UNSIGNED_BYTE;
|
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(glInternalFormat, PixelFormatNormalizeOptionBit::None,
|
|
&realInternalFormat, &format, &type);
|
|
const SizeT readBytesPerTexel =
|
|
MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(format),
|
|
MG_Util::ConvertGLEnumToTexturePixelDataType(type));
|
|
if (readBytesPerTexel != bytesPerTexel) {
|
|
MGLOG_I("%s: cannot copy into a %zu-byte texel from a %zu-byte readback layout", caller,
|
|
bytesPerTexel, readBytesPerTexel);
|
|
return false;
|
|
}
|
|
|
|
Vector<Uint8> scratch(static_cast<SizeT>(width) * static_cast<SizeT>(height) * bytesPerTexel);
|
|
{
|
|
ScopedNeutralPackState neutralPack;
|
|
MG_Backend::gBackendFunctionsTable.GL.ReadPixels(x, y, width, height, format, type, scratch.data());
|
|
}
|
|
|
|
auto* destination =
|
|
static_cast<Uint8*>(mipmapTexture->MapMipmapData(uploadTarget, static_cast<Uint>(level)));
|
|
if (!destination) return false;
|
|
|
|
const SizeT fullRowBytes = static_cast<SizeT>(texelSize.x()) * bytesPerTexel;
|
|
const SizeT fullSliceBytes = static_cast<SizeT>(texelSize.y()) * fullRowBytes;
|
|
const SizeT copyRowBytes = static_cast<SizeT>(width) * bytesPerTexel;
|
|
for (GLsizei row = 0; row < height; ++row) {
|
|
Uint8* dst = destination + static_cast<SizeT>(zoffset) * fullSliceBytes +
|
|
static_cast<SizeT>(yoffset + row) * fullRowBytes +
|
|
static_cast<SizeT>(xoffset) * bytesPerTexel;
|
|
Memcpy(dst, scratch.data() + static_cast<SizeT>(row) * copyRowBytes, copyRowBytes);
|
|
}
|
|
mipmapTexture->MarkStorageDirty(uploadTarget, static_cast<Uint>(level), true);
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
void ClearTexImage(GLuint texture, GLint level, GLenum format, GLenum type, const void* data) {
|
|
auto textureObject = GetClearTextureObject(texture, level, __func__);
|
|
if (!textureObject) return;
|
|
|
|
Vector<Uint8> clearPixel;
|
|
if (!BuildClearPixel(textureObject, format, type, data, clearPixel)) return;
|
|
|
|
for (TextureUploadTarget uploadTarget : textureObject->GetUploadTargets()) {
|
|
const IntVec3 size = textureObject->GetMipmapTexelSize(uploadTarget, static_cast<Uint>(level));
|
|
if (!ClearMipmapRegion(textureObject, uploadTarget, level, 0, 0, 0,
|
|
size.x(), size.y(), size.z(), clearPixel, __func__)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClearTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type,
|
|
const void* data) {
|
|
auto textureObject = GetClearTextureObject(texture, level, __func__);
|
|
if (!textureObject) return;
|
|
|
|
Vector<Uint8> clearPixel;
|
|
if (!BuildClearPixel(textureObject, format, type, data, clearPixel)) return;
|
|
|
|
const auto& uploadTargets = textureObject->GetUploadTargets();
|
|
if (textureObject->GetTarget() == TextureTarget::TextureCubeMap) {
|
|
if (zoffset < 0 || depth < 0 ||
|
|
static_cast<SizeT>(zoffset) > uploadTargets.size() ||
|
|
static_cast<SizeT>(depth) > uploadTargets.size() - static_cast<SizeT>(zoffset)) {
|
|
RecordClearTextureError(__func__, ErrorCode::InvalidValue,
|
|
"The cube-map clear region selects invalid faces.");
|
|
return;
|
|
}
|
|
for (GLsizei face = 0; face < depth; ++face) {
|
|
if (!ClearMipmapRegion(textureObject, uploadTargets[static_cast<SizeT>(zoffset + face)], level,
|
|
xoffset, yoffset, 0, width, height, 1, clearPixel, __func__)) {
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (uploadTargets.empty()) {
|
|
RecordClearTextureError(__func__, ErrorCode::InvalidOperation,
|
|
"The requested texture has no upload target.");
|
|
return;
|
|
}
|
|
ClearMipmapRegion(textureObject, uploadTargets.front(), level, xoffset, yoffset, zoffset,
|
|
width, height, depth, clearPixel, __func__);
|
|
}
|
|
|
|
Bool ValidateTextureParameterForTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
GLenum pname, GLint param, const char* caller) {
|
|
const auto target = textureObject->GetTarget();
|
|
if (pname == GL_TEXTURE_MAX_ANISOTROPY_EXT && !ValidateMaxAnisotropy(param, caller)) {
|
|
return false;
|
|
}
|
|
if ((pname == GL_TEXTURE_BASE_LEVEL || pname == GL_TEXTURE_MAX_LEVEL) && param < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture level parameter must be non-negative."));
|
|
return false;
|
|
}
|
|
|
|
if ((target == TextureTarget::Texture2DMultisample ||
|
|
target == TextureTarget::Texture2DMultisampleArray) &&
|
|
pname == GL_TEXTURE_BASE_LEVEL && param != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Multisample texture base level must be zero."));
|
|
return false;
|
|
}
|
|
|
|
if (target == TextureTarget::TextureRectangle && pname == GL_TEXTURE_BASE_LEVEL && param != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Rectangle texture base level must be zero."));
|
|
return false;
|
|
}
|
|
|
|
if ((target == TextureTarget::Texture2DMultisample ||
|
|
target == TextureTarget::Texture2DMultisampleArray) &&
|
|
(pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T || pname == GL_TEXTURE_WRAP_R ||
|
|
pname == GL_TEXTURE_MIN_FILTER || pname == GL_TEXTURE_MAG_FILTER || pname == GL_TEXTURE_MIN_LOD ||
|
|
pname == GL_TEXTURE_MAX_LOD || pname == GL_TEXTURE_LOD_BIAS || pname == GL_TEXTURE_COMPARE_MODE ||
|
|
pname == GL_TEXTURE_COMPARE_FUNC || pname == GL_TEXTURE_BORDER_COLOR ||
|
|
pname == GL_TEXTURE_MAX_ANISOTROPY_EXT)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Sampler state is invalid for multisample textures."));
|
|
return false;
|
|
}
|
|
|
|
if (target == TextureTarget::TextureRectangle) {
|
|
if ((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) &&
|
|
(param == GL_MIRROR_CLAMP_TO_EDGE || param == GL_MIRRORED_REPEAT || param == GL_REPEAT)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Invalid wrap mode for rectangle texture."));
|
|
return false;
|
|
}
|
|
if (pname == GL_TEXTURE_MIN_FILTER && param != GL_NEAREST && param != GL_LINEAR) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Invalid min filter for rectangle texture."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TextureUploadTarget GetPrimaryUploadTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject) {
|
|
if (!textureObject) return TextureUploadTarget::Unknown;
|
|
const auto& uploadTargets = textureObject->GetUploadTargets();
|
|
return uploadTargets.empty() ? TextureUploadTarget::Unknown : uploadTargets[0];
|
|
}
|
|
|
|
void TextureParameterObject_State(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLenum pname,
|
|
GLint param, const char* caller) {
|
|
if (!textureObject) return;
|
|
if (!ValidateTextureParameterForTarget(textureObject, pname, param, caller)) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
textureObject->GetSamplerObject()->SetMagFilter(MG_Util::ConvertGLEnumToSamplerFilterMode(param));
|
|
break;
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
textureObject->GetSamplerObject()->SetMinFilter(MG_Util::ConvertGLEnumToSamplerFilterMode(param));
|
|
textureObject->GetSamplerObject()->SetMipmapMode(MG_Util::ConvertGLEnumToSamplerMipmapMode(param));
|
|
break;
|
|
case GL_TEXTURE_MIN_LOD: {
|
|
Float maxLod = textureObject->GetSamplerObject()->GetMaxLod();
|
|
textureObject->GetSamplerObject()->SetLodRange(param, maxLod);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_MAX_LOD: {
|
|
Float minLod = textureObject->GetSamplerObject()->GetMinLod();
|
|
textureObject->GetSamplerObject()->SetLodRange(minLod, param);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_BASE_LEVEL:
|
|
textureObject->SetBaseLevel(param);
|
|
break;
|
|
case GL_TEXTURE_MAX_LEVEL:
|
|
textureObject->SetMaxLevel(param);
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_R:
|
|
case GL_TEXTURE_SWIZZLE_G:
|
|
case GL_TEXTURE_SWIZZLE_B:
|
|
case GL_TEXTURE_SWIZZLE_A: {
|
|
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
|
|
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam(param);
|
|
if (swizzleValue == TextureSwizzleParam::Unknown) {
|
|
// GL CTS texture_swizzle.api_errors: single-value TexParameter* with a value outside
|
|
// [RED, GREEN, BLUE, ALPHA, ZERO, ONE] must raise GL_INVALID_ENUM.
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Invalid texture swizzle value."));
|
|
return;
|
|
}
|
|
textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_WRAP_S:
|
|
textureObject->GetSamplerObject()->SetWrapS(MG_Util::ConvertGLEnumToSamplerWrapMode(param));
|
|
break;
|
|
case GL_TEXTURE_WRAP_T:
|
|
textureObject->GetSamplerObject()->SetWrapT(MG_Util::ConvertGLEnumToSamplerWrapMode(param));
|
|
break;
|
|
case GL_TEXTURE_WRAP_R:
|
|
textureObject->GetSamplerObject()->SetWrapR(MG_Util::ConvertGLEnumToSamplerWrapMode(param));
|
|
break;
|
|
case GL_TEXTURE_COMPARE_MODE:
|
|
textureObject->GetSamplerObject()->SetCompareMode(MG_Util::ConvertGLEnumToSamplerCompareMode(param));
|
|
break;
|
|
case GL_TEXTURE_COMPARE_FUNC:
|
|
textureObject->GetSamplerObject()->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(param));
|
|
break;
|
|
case GL_TEXTURE_LOD_BIAS:
|
|
textureObject->GetSamplerObject()->SetLodBias((GLfloat)param);
|
|
break;
|
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
|
textureObject->GetSamplerObject()->SetMaxAnisotropy(static_cast<GLfloat>(param));
|
|
break;
|
|
case GL_GENERATE_MIPMAP:
|
|
g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != GL_FALSE);
|
|
break;
|
|
case GL_DEPTH_STENCIL_TEXTURE_MODE:
|
|
if (param != GL_DEPTH_COMPONENT && param != GL_STENCIL_INDEX) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Invalid GL_DEPTH_STENCIL_TEXTURE_MODE value."));
|
|
return;
|
|
}
|
|
textureObject->SetDepthStencilTextureMode(static_cast<GLenum>(param));
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("pname {} is not a valid texture parameter.", MG_Util::ConvertGLEnumToString(pname))));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TextureParameterObjectf_State(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLenum pname,
|
|
GLfloat param, const char* caller) {
|
|
if (!textureObject) return;
|
|
if (pname == GL_TEXTURE_MAX_ANISOTROPY_EXT && !ValidateMaxAnisotropy(param, caller)) return;
|
|
const GLint validationParam =
|
|
pname == GL_TEXTURE_MAX_ANISOTROPY_EXT ? 1 : static_cast<GLint>(param);
|
|
if (!ValidateTextureParameterForTarget(textureObject, pname, validationParam, caller)) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
textureObject->GetSamplerObject()->SetMagFilter(MG_Util::ConvertGLEnumToSamplerFilterMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
textureObject->GetSamplerObject()->SetMinFilter(MG_Util::ConvertGLEnumToSamplerFilterMode((GLenum)param));
|
|
textureObject->GetSamplerObject()->SetMipmapMode(MG_Util::ConvertGLEnumToSamplerMipmapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_MIN_LOD: {
|
|
Float maxLod = textureObject->GetSamplerObject()->GetMaxLod();
|
|
textureObject->GetSamplerObject()->SetLodRange(param, maxLod);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_MAX_LOD: {
|
|
Float minLod = textureObject->GetSamplerObject()->GetMinLod();
|
|
textureObject->GetSamplerObject()->SetLodRange(minLod, param);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_BASE_LEVEL:
|
|
textureObject->SetBaseLevel((Uint)param);
|
|
break;
|
|
case GL_TEXTURE_MAX_LEVEL:
|
|
textureObject->SetMaxLevel((Uint)param);
|
|
break;
|
|
case GL_TEXTURE_WRAP_S:
|
|
textureObject->GetSamplerObject()->SetWrapS(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_WRAP_T:
|
|
textureObject->GetSamplerObject()->SetWrapT(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_WRAP_R:
|
|
textureObject->GetSamplerObject()->SetWrapR(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_COMPARE_MODE:
|
|
textureObject->GetSamplerObject()->SetCompareMode(
|
|
MG_Util::ConvertGLEnumToSamplerCompareMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_COMPARE_FUNC:
|
|
textureObject->GetSamplerObject()->SetSamplerCompareFunc(
|
|
MG_Util::ConvertGLEnumToSamplerCompareFunc((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_LOD_BIAS:
|
|
textureObject->GetSamplerObject()->SetLodBias(param);
|
|
break;
|
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
|
textureObject->GetSamplerObject()->SetMaxAnisotropy(param);
|
|
break;
|
|
case GL_GENERATE_MIPMAP:
|
|
g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != 0.0f);
|
|
break;
|
|
case GL_DEPTH_STENCIL_TEXTURE_MODE:
|
|
if (param != GL_DEPTH_COMPONENT && param != GL_STENCIL_INDEX) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Invalid GL_DEPTH_STENCIL_TEXTURE_MODE value."));
|
|
return;
|
|
}
|
|
textureObject->SetDepthStencilTextureMode(static_cast<GLenum>(param));
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("pname {} is not a valid texture parameter.", MG_Util::ConvertGLEnumToString(pname))));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void GetTextureParameterObjectiv_State(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
GLenum pname, GLint* params, const char* caller) {
|
|
if (!textureObject || !params) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
*params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(textureObject->GetSamplerObject()->GetMagFilter(),
|
|
SamplerMipmapMode::None);
|
|
break;
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
*params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetMinFilter(), textureObject->GetSamplerObject()->GetMipmapMode());
|
|
break;
|
|
case GL_TEXTURE_MIN_LOD:
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMinLod());
|
|
break;
|
|
case GL_TEXTURE_MAX_LOD:
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxLod());
|
|
break;
|
|
case GL_TEXTURE_BASE_LEVEL:
|
|
*params = static_cast<GLint>(textureObject->GetLevelRange().x());
|
|
break;
|
|
case GL_TEXTURE_MAX_LEVEL:
|
|
*params = static_cast<GLint>(textureObject->GetLevelRange().y());
|
|
break;
|
|
case GL_TEXTURE_IMMUTABLE_FORMAT:
|
|
*params = textureObject->IsImmutable() ? GL_TRUE : GL_FALSE;
|
|
break;
|
|
case GL_TEXTURE_IMMUTABLE_LEVELS:
|
|
*params = static_cast<GLint>(textureObject->GetImmutableLevels());
|
|
break;
|
|
case GL_TEXTURE_WRAP_S:
|
|
*params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapS());
|
|
break;
|
|
case GL_TEXTURE_WRAP_T:
|
|
*params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapT());
|
|
break;
|
|
case GL_TEXTURE_WRAP_R:
|
|
*params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapR());
|
|
break;
|
|
case GL_TEXTURE_COMPARE_MODE:
|
|
*params =
|
|
(GLint)MG_Util::ConvertSamplerCompareModeToGLEnum(textureObject->GetSamplerObject()->GetCompareMode());
|
|
break;
|
|
case GL_TEXTURE_COMPARE_FUNC:
|
|
*params = (GLint)MG_Util::ConvertSamplerCompareFuncToGLEnum(
|
|
textureObject->GetSamplerObject()->GetSamplerCompareFunc());
|
|
break;
|
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxAnisotropy());
|
|
break;
|
|
case GL_DEPTH_STENCIL_TEXTURE_MODE:
|
|
*params = static_cast<GLint>(textureObject->GetDepthStencilTextureMode());
|
|
break;
|
|
case GL_TEXTURE_LOD_BIAS:
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetLodBias());
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_R:
|
|
case GL_TEXTURE_SWIZZLE_G:
|
|
case GL_TEXTURE_SWIZZLE_B:
|
|
case GL_TEXTURE_SWIZZLE_A: {
|
|
const auto component = static_cast<SizeT>(pname - GL_TEXTURE_SWIZZLE_R);
|
|
*params = static_cast<GLint>(
|
|
MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetAllSwizzleParams()[component]));
|
|
break;
|
|
}
|
|
case GL_TEXTURE_TARGET:
|
|
*params = static_cast<GLint>(MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget()));
|
|
break;
|
|
case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE:
|
|
*params = GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE;
|
|
break;
|
|
// Texture views are not implemented; a texture that is not a view reports the defaults
|
|
// GL 4.6 core table 23.17 gives (0 layers/levels of offset, and its own extent).
|
|
case GL_TEXTURE_VIEW_MIN_LEVEL:
|
|
case GL_TEXTURE_VIEW_MIN_LAYER:
|
|
*params = 0;
|
|
break;
|
|
case GL_TEXTURE_VIEW_NUM_LEVELS:
|
|
case GL_TEXTURE_VIEW_NUM_LAYERS:
|
|
*params = 0;
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("pname {} is not a valid texture parameter.",
|
|
MG_Util::ConvertGLEnumToString(pname))));
|
|
return;
|
|
}
|
|
}
|
|
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByTarget(
|
|
TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) {
|
|
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
|
|
auto& textureObject =
|
|
TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget);
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject;
|
|
return textureObject;
|
|
} else {
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject;
|
|
return textureObject;
|
|
}
|
|
}
|
|
|
|
// Texture-parameter lookups must not raise GL_INVALID_OPERATION when the default texture
|
|
// (name 0) is bound: glTexParameter* on default textures is legal GL (the GL CTS state reset
|
|
// sets swizzles/levels on texture 0 for every unit x target and expects glGetError() to stay
|
|
// clean). Name 0 resolves to the target's real default texture object, so parameters set on
|
|
// it are stored and queryable like on any texture.
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByTargetForParameter(
|
|
TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) {
|
|
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
|
|
return TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget);
|
|
}
|
|
if (textureTarget == TextureTarget::Unknown) {
|
|
return nullTextureObject;
|
|
}
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
return activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
|
}
|
|
|
|
void GenerateMipmap_Backend(GLenum target) {
|
|
MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap(target);
|
|
}
|
|
|
|
void MaybeAutoGenerateMipmap(GLenum target, const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
Bool isProxy, GLint level) {
|
|
if (isProxy || level != 0 || !textureObject) {
|
|
return;
|
|
}
|
|
const auto it = g_autoGenerateMipmapByTextureId.find(textureObject->GetExternalIndex());
|
|
if (it == g_autoGenerateMipmapByTextureId.end() || !it->second) {
|
|
return;
|
|
}
|
|
GenerateMipmap_Backend(target);
|
|
}
|
|
|
|
// GL 4.6 core 8.5: sourcing an upload from a bound PIXEL_UNPACK_BUFFER adds three
|
|
// INVALID_OPERATION conditions that do not exist for client memory. `pixels` is a byte offset
|
|
// into that buffer, not a pointer. Returns true when no unpack buffer is bound, so every caller
|
|
// can run it unconditionally.
|
|
Bool ValidatePixelUnpackBufferSource(const void* pixels, TextureInputFormat inputFormat,
|
|
TexturePixelDataType dataType, IntVec3 dimension, const char* caller) {
|
|
const auto& unpackBuffer =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (!unpackBuffer) return true;
|
|
|
|
// Persistent mappings remain legal transfer sources, as on the pack side in ReadPixels.
|
|
if (unpackBuffer->IsMapped() && !(unpackBuffer->GetMappingAccess() & BufferMappingAccessBit::Persistent)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Pixel unpack buffer is currently mapped."));
|
|
return false;
|
|
}
|
|
|
|
const SizeT offset = reinterpret_cast<SizeT>(pixels);
|
|
const SizeT typeSize = MG_Util::GetTexturePixelDataTypeSize(dataType);
|
|
if (typeSize != 0 && (offset % typeSize) != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Pixel unpack buffer offset must be a multiple of the size of a datum "
|
|
"of the given type."));
|
|
return false;
|
|
}
|
|
|
|
// The tightly packed span is the smallest the unpack can read, so a request that overruns
|
|
// even this one certainly overruns the store; pixel store parameters only ever widen it.
|
|
const SizeT bufferSize = unpackBuffer->GetSize();
|
|
const SizeT required = MG_Util::CalculateInputTextureImageSize(inputFormat, dataType, dimension);
|
|
if (offset > bufferSize || required > bufferSize - offset) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Unpacking would read past the end of the pixel unpack buffer."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height, zoffset,
|
|
depth))
|
|
return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureObject->GetFormat(),
|
|
texturePixelDataType))
|
|
return;
|
|
if (!ValidatePixelUnpackBufferSource(pixels, textureInputFormat, texturePixelDataType, {width, height, depth},
|
|
__func__))
|
|
return;
|
|
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level is out of range."));
|
|
return;
|
|
}
|
|
|
|
const void* originalPixels = pixels;
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
if (!originalPixels) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"No data supplied from pixels parameter and no PBO bound."));
|
|
return;
|
|
}
|
|
|
|
SizeT inputSize = 0;
|
|
void* processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureObject->GetFormat(),
|
|
textureInputFormat, texturePixelDataType, {width, height, depth}, false, inputSize);
|
|
if (!processedPixels || inputSize == 0) {
|
|
if (processedPixels) free(processedPixels);
|
|
return;
|
|
}
|
|
|
|
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level);
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureObject->GetFormat(), texturePixelDataType);
|
|
const SizeT srcRowSize = static_cast<SizeT>(width) * internalBpp;
|
|
const SizeT srcSliceSize = static_cast<SizeT>(height) * srcRowSize;
|
|
const SizeT destRowSize = static_cast<SizeT>(texelSize.x()) * internalBpp;
|
|
const SizeT destSliceSize = static_cast<SizeT>(texelSize.y()) * destRowSize;
|
|
|
|
if (xoffset + width > static_cast<GLsizei>(texelSize.x()) ||
|
|
yoffset + height > static_cast<GLsizei>(texelSize.y()) ||
|
|
zoffset + depth > static_cast<GLsizei>(texelSize.z())) {
|
|
MGLOG_E("TexSubImage3D_State: Specified region exceeds texture level dimensions");
|
|
free(processedPixels);
|
|
return;
|
|
}
|
|
|
|
const auto* srcData = static_cast<const Uint8*>(processedPixels);
|
|
Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level));
|
|
if (destData) {
|
|
for (GLsizei z = 0; z < depth; ++z) {
|
|
for (GLsizei y = 0; y < height; ++y) {
|
|
const SizeT destRowOffset =
|
|
static_cast<SizeT>(zoffset + z) * destSliceSize +
|
|
static_cast<SizeT>(yoffset + y) * destRowSize +
|
|
static_cast<SizeT>(xoffset) * internalBpp;
|
|
const SizeT srcRowOffset =
|
|
static_cast<SizeT>(z) * srcSliceSize + static_cast<SizeT>(y) * srcRowSize;
|
|
Memcpy(destData + destRowOffset, srcData + srcRowOffset, srcRowSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(processedPixels);
|
|
textureMipmapObject->MarkStorageDirtyRegion(textureUploadTarget, level, {xoffset, yoffset, zoffset},
|
|
{width, height, depth});
|
|
MaybeAutoGenerateMipmap(target, textureObject, false, level);
|
|
}
|
|
|
|
void TexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
|
|
GLenum format, GLenum type, const void* pixels) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
// TextureInternalFormat textureInternalFormat =
|
|
// MG_Util::ConvertGLEnumToTextureInternalFormat(format);
|
|
MGLOG_D("TexSubImage2D_State: target = %s, level = %d, (%d, %d), format = %s, pixels = %p",
|
|
MG_Util::ConvertGLEnumToString(target).c_str(), level, width, height,
|
|
MG_Util::ConvertTextureInputFormatToString(textureInputFormat).c_str(), pixels);
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
if (!ValidatePixelUnpackBufferSource(pixels, textureInputFormat, texturePixelDataType, {width, height, 1},
|
|
__func__))
|
|
return;
|
|
|
|
// ======================= Processing ================================
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
TextureInternalFormat textureInternalFormat = textureObject->GetFormat();
|
|
MGLOG_D("%s: working on texture %d", __func__, textureObject->GetExternalIndex());
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat,
|
|
texturePixelDataType))
|
|
return;
|
|
|
|
// ======================= Processing ================================
|
|
// Texture object here should always be an object with mipmap
|
|
// Assert this for extra safety.
|
|
// This should automatically compiled out in release,
|
|
// so that we don't take the perf hit of dyn-cast.
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level is out of range."));
|
|
return;
|
|
}
|
|
auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level);
|
|
|
|
SizeT inputSize = 0;
|
|
|
|
const void* originalPixels = pixels;
|
|
|
|
// PBO
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
MGLOG_D("TexSubImage2D_State: Using Pixel Unpack Buffer Object ID: %u",
|
|
pixelUnpackBufferObject->GetExternalIndex());
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
|
|
if (!originalPixels) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"No data supplied from pixels parameter and no PBO bound."));
|
|
return;
|
|
}
|
|
const auto& unpackParams = MG_State::pGLContext->GetPixelStoreParameters(true);
|
|
void* processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, unpackParams, textureInternalFormat, textureInputFormat, texturePixelDataType,
|
|
{width, height, 1}, false, inputSize);
|
|
|
|
if (!processedPixels || inputSize == 0) {
|
|
MGLOG_E("TexSubImage2D_State: Failed to process pixel data for TexSubImage2D, width: %d, height: %d", width,
|
|
height);
|
|
if (processedPixels) free(processedPixels);
|
|
return;
|
|
}
|
|
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType);
|
|
|
|
const SizeT srcRowSize = static_cast<SizeT>(width) * internalBpp;
|
|
const SizeT srcStride = srcRowSize;
|
|
const SizeT destRowSize = static_cast<SizeT>(texelSize.x()) * internalBpp;
|
|
|
|
if (xoffset + width > static_cast<GLsizei>(texelSize.x()) ||
|
|
yoffset + height > static_cast<GLsizei>(texelSize.y())) {
|
|
MGLOG_E("TexSubImage2D_State: Specified region exceeds texture dimensions");
|
|
free(processedPixels);
|
|
return;
|
|
}
|
|
|
|
const auto* srcData = static_cast<const Uint8*>(processedPixels);
|
|
Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level));
|
|
|
|
if (destData) {
|
|
for (GLsizei y = 0; y < height; y++) {
|
|
const SizeT destRowOffset = (yoffset + y) * destRowSize + xoffset * internalBpp;
|
|
const SizeT srcRowOffset = y * srcStride;
|
|
Memcpy(destData + destRowOffset, srcData + srcRowOffset, srcRowSize);
|
|
}
|
|
}
|
|
|
|
free(processedPixels);
|
|
|
|
MGLOG_D("%s: mark mip %d as dirty", __func__, level);
|
|
textureMipmapObject->MarkStorageDirtyRegion(textureUploadTarget, level, {xoffset, yoffset, 0},
|
|
{width, height, 1});
|
|
MaybeAutoGenerateMipmap(target, textureObject, false, level);
|
|
}
|
|
|
|
void TexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type,
|
|
const GLvoid* pixels) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, 1)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, 1, 1)) return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureObject->GetFormat(),
|
|
texturePixelDataType))
|
|
return;
|
|
if (!ValidatePixelUnpackBufferSource(pixels, textureInputFormat, texturePixelDataType, {width, 1, 1}, __func__))
|
|
return;
|
|
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
|
|
const void* originalPixels = pixels;
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
if (!originalPixels) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"No data supplied from pixels parameter and no PBO bound."));
|
|
return;
|
|
}
|
|
|
|
SizeT inputSize = 0;
|
|
void* processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureObject->GetFormat(),
|
|
textureInputFormat, texturePixelDataType, {width, 1, 1}, false, inputSize);
|
|
if (!processedPixels || inputSize == 0) {
|
|
if (processedPixels) free(processedPixels);
|
|
return;
|
|
}
|
|
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureObject->GetFormat(), texturePixelDataType);
|
|
const SizeT copySize = static_cast<SizeT>(width) * internalBpp;
|
|
Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level));
|
|
if (destData) {
|
|
Memcpy(destData + static_cast<SizeT>(xoffset) * internalBpp, processedPixels, copySize);
|
|
}
|
|
|
|
free(processedPixels);
|
|
textureMipmapObject->MarkStorageDirtyRegion(textureUploadTarget, level, {xoffset, 0, 0}, {width, 1, 1});
|
|
MaybeAutoGenerateMipmap(target, textureObject, false, level);
|
|
}
|
|
|
|
// TexParameteriv/TexParameterfv are introduced in OpenGL 4.0, so do not support them for now.
|
|
void TexParameterf_State(GLenum target, GLenum pname, GLfloat param) {
|
|
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
|
|
// The per-object setters run this before writing, and glTexParameterfv/glTextureParameterfv
|
|
// funnel everything that is not a vector pname down here - so without it the float forms of
|
|
// the setter accepted sampler state on a multisample texture, a mipmapping filter on a
|
|
// rectangle texture and a negative base level, all of which the integer forms rejected.
|
|
const GLint validationParam = pname == GL_TEXTURE_MAX_ANISOTROPY_EXT ? 1 : static_cast<GLint>(param);
|
|
if (!ValidateTextureParameterForTarget(textureObject, pname, validationParam, __func__)) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
textureObject->GetSamplerObject()->SetMagFilter(MG_Util::ConvertGLEnumToSamplerFilterMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
textureObject->GetSamplerObject()->SetMinFilter(MG_Util::ConvertGLEnumToSamplerFilterMode((GLenum)param));
|
|
textureObject->GetSamplerObject()->SetMipmapMode(MG_Util::ConvertGLEnumToSamplerMipmapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_MIN_LOD: {
|
|
Float maxLod = textureObject->GetSamplerObject()->GetMaxLod();
|
|
textureObject->GetSamplerObject()->SetLodRange(param, maxLod);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_MAX_LOD: {
|
|
Float minLod = textureObject->GetSamplerObject()->GetMinLod();
|
|
textureObject->GetSamplerObject()->SetLodRange(minLod, param);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_BASE_LEVEL:
|
|
textureObject->SetBaseLevel((Uint)param);
|
|
break;
|
|
case GL_TEXTURE_MAX_LEVEL:
|
|
textureObject->SetMaxLevel((Uint)param);
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_R:
|
|
case GL_TEXTURE_SWIZZLE_G:
|
|
case GL_TEXTURE_SWIZZLE_B:
|
|
case GL_TEXTURE_SWIZZLE_A: {
|
|
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
|
|
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam((GLenum)param);
|
|
if (swizzleValue == TextureSwizzleParam::Unknown) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Invalid texture swizzle value."));
|
|
return;
|
|
}
|
|
textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_WRAP_S:
|
|
textureObject->GetSamplerObject()->SetWrapS(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_WRAP_T:
|
|
textureObject->GetSamplerObject()->SetWrapT(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_WRAP_R:
|
|
textureObject->GetSamplerObject()->SetWrapR(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_COMPARE_MODE:
|
|
textureObject->GetSamplerObject()->SetCompareMode(
|
|
MG_Util::ConvertGLEnumToSamplerCompareMode((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_COMPARE_FUNC:
|
|
textureObject->GetSamplerObject()->SetSamplerCompareFunc(
|
|
MG_Util::ConvertGLEnumToSamplerCompareFunc((GLenum)param));
|
|
break;
|
|
case GL_TEXTURE_LOD_BIAS:
|
|
textureObject->GetSamplerObject()->SetLodBias(param);
|
|
break;
|
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
|
if (!ValidateMaxAnisotropy(param, __func__)) return;
|
|
textureObject->GetSamplerObject()->SetMaxAnisotropy(param);
|
|
break;
|
|
case GL_GENERATE_MIPMAP:
|
|
g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != 0.0f);
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_RGBA:
|
|
// Not supported in this function
|
|
case GL_TEXTURE_BORDER_COLOR:
|
|
// Not supported in this function
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", __func__,
|
|
std::format("pname {} is not a valid texture parameter.", MG_Util::ConvertGLEnumToString(pname))));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TexParameteri_State(GLenum target, GLenum pname, GLint param) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
|
|
TextureParameterObject_State(textureObject, pname, param, __func__);
|
|
}
|
|
|
|
// Quick and dirty TexParameter*v implementation to make NeoForge happy.
|
|
// TODO: implement the missing part
|
|
void TexParameterfv_State(GLenum target, GLenum pname, const GLfloat* params) {
|
|
switch (pname) {
|
|
case GL_TEXTURE_BORDER_COLOR: {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
SetTextureBorderColorFromFloats(textureObject, params);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
|
|
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
|
|
if (!SetTextureSwizzleParamsFromInts(textureObject, signedParams, __func__)) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
TexParameterf_State(target, pname, *params);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TexParameteriv_State(GLenum target, GLenum pname, const GLint* params) {
|
|
switch (pname) {
|
|
case GL_TEXTURE_BORDER_COLOR: {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
SetTextureBorderColorFromInts(textureObject, params);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
if (!SetTextureSwizzleParamsFromInts(textureObject, params, __func__)) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
TexParameteri_State(target, pname, *params);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TexParameterIiv_State(GLenum target, GLenum pname, const GLint* params) {
|
|
switch (pname) {
|
|
case GL_TEXTURE_BORDER_COLOR: {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
SetTextureBorderColorFromIntegerInts(textureObject, params);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
|
|
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
|
|
if (!SetTextureSwizzleParamsFromInts(textureObject, signedParams, __func__)) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
TexParameteri_State(target, pname, *params);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TexParameterIuiv_State(GLenum target, GLenum pname, const GLuint* params) {
|
|
switch (pname) {
|
|
case GL_TEXTURE_BORDER_COLOR: {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
SetTextureBorderColorFromUnsignedInts(textureObject, params);
|
|
break;
|
|
}
|
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
|
if (!textureObject) return;
|
|
|
|
Vec4<TextureSwizzleParam> swizzleParams;
|
|
for (int i = 0; i < 4; i++) {
|
|
swizzleParams[i] = MG_Util::ConvertGLEnumToTextureSwizzleParam(static_cast<GLint>(params[i]));
|
|
if (TextureSwizzleParam::Unknown == swizzleParams[i]) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "`params` is not valid."));
|
|
return;
|
|
}
|
|
}
|
|
textureObject->SetSwizzleParamRGBA(swizzleParams);
|
|
break;
|
|
}
|
|
default:
|
|
TexParameteri_State(target, pname, static_cast<GLint>(*params));
|
|
break;
|
|
}
|
|
}
|
|
|
|
Bool TexImage3DMultisample_State(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return false;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return false;
|
|
if (textureTarget != TextureTarget::Texture2DMultisampleArray) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Target must be GL_TEXTURE_2D_MULTISAMPLE_ARRAY or its proxy."));
|
|
return false;
|
|
}
|
|
|
|
textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, TextureInputFormat::RGBA,
|
|
TexturePixelDataType::UnsignedByte);
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return false;
|
|
if (!ValidateTextureMultisampleStorage(textureTarget, samples, width, height, depth, textureInternalFormat,
|
|
__func__))
|
|
return false;
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
const Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
// Name 0 resolves to the target's default texture object - a real texture this call
|
|
// (re)specifies like any other; the slot is never empty anymore.
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return false;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return false;
|
|
}
|
|
|
|
AllocateMultisampleTextureStorage(textureObject, textureUploadTarget, textureInternalFormat, samples, width,
|
|
height, depth, fixedsamplelocations);
|
|
return true;
|
|
}
|
|
|
|
Bool TexImage2DMultisample_State(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLboolean fixedsamplelocations) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return false;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return false;
|
|
if (textureTarget != TextureTarget::Texture2DMultisample) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Target must be GL_TEXTURE_2D_MULTISAMPLE or its proxy."));
|
|
return false;
|
|
}
|
|
|
|
textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, TextureInputFormat::RGBA,
|
|
TexturePixelDataType::UnsignedByte);
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return false;
|
|
if (!ValidateTextureMultisampleStorage(textureTarget, samples, width, height, 1, textureInternalFormat,
|
|
__func__))
|
|
return false;
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
const Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
// Name 0 resolves to the target's default texture object - a real texture this call
|
|
// (re)specifies like any other; the slot is never empty anymore.
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return false;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return false;
|
|
}
|
|
|
|
AllocateMultisampleTextureStorage(textureObject, textureUploadTarget, textureInternalFormat, samples, width,
|
|
height, 1, fixedsamplelocations);
|
|
return true;
|
|
}
|
|
|
|
void TexImage3D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height,
|
|
GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels) {
|
|
MGLOG_D(
|
|
"%s called with target: %s, level: %d, internalformat: %s, width: %d, height: %d, depth: %d, "
|
|
"border: %d, format: %s, type: %s (%u), pixels: %p",
|
|
__func__,
|
|
MG_Util::ConvertTextureUploadTargetToString(MG_Util::ConvertGLEnumToTextureUploadTarget(target)).c_str(),
|
|
level,
|
|
MG_Util::ConvertTextureInternalFormatToString(MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat))
|
|
.c_str(),
|
|
width, height, depth, border,
|
|
MG_Util::ConvertTextureInputFormatToString(MG_Util::ConvertGLEnumToTextureInputFormat(format)).c_str(),
|
|
MG_Util::ConvertTexturePixelDataTypeToString(MG_Util::ConvertGLEnumToTexturePixelDataType(type)).c_str(),
|
|
type, pixels);
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
|
if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat,
|
|
texturePixelDataType))
|
|
return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
// Depth and depth-stencil formats are not three-dimensional in core GL (2D-array targets are fine).
|
|
if ((textureUploadTarget == TextureUploadTarget::Texture3D ||
|
|
textureUploadTarget == TextureUploadTarget::ProxyTexture3D) &&
|
|
(textureInputFormat == TextureInputFormat::DepthComponent ||
|
|
textureInputFormat == TextureInputFormat::DepthStencil)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Depth formats are invalid for 3D texture targets"));
|
|
return;
|
|
}
|
|
|
|
// RGTC is a 2D-only compression scheme, so a 3D target rejects it. This has to be tested on
|
|
// the raw enum: the RGTC formats resolve to plain R8/RG8/SNORM storage on the way in (see
|
|
// GLToMG's TextureEnumConverter), so once the internal format is converted there is nothing
|
|
// left to distinguish them from an ordinary one- or two-channel upload.
|
|
if ((textureUploadTarget == TextureUploadTarget::Texture3D ||
|
|
textureUploadTarget == TextureUploadTarget::ProxyTexture3D) &&
|
|
(internalformat == GL_COMPRESSED_RED_RGTC1 || internalformat == GL_COMPRESSED_SIGNED_RED_RGTC1 ||
|
|
internalformat == GL_COMPRESSED_RG_RGTC2 || internalformat == GL_COMPRESSED_SIGNED_RG_RGTC2)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"RGTC compressed formats are invalid for 3D texture targets"));
|
|
return;
|
|
}
|
|
|
|
// TODO: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the
|
|
// GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped.
|
|
// GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER
|
|
// target and the data would be unpacked from the buffer object such that the memory reads required would
|
|
// exceed the data store size.
|
|
// GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER
|
|
// target and data is not evenly divisible into the number of bytes needed to store in memory a datum
|
|
// indicated by type.
|
|
// ======================= Processing ================================
|
|
textureInternalFormat =
|
|
MG_Util::ConvertInternalFormatToSized(textureInternalFormat, textureInputFormat, texturePixelDataType);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
// ===================== Error Checking ==============================
|
|
// Name 0 resolves to the target's default texture object - a real texture this call
|
|
// (re)specifies like any other; the slot is never empty anymore.
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
// ======================= Processing ================================
|
|
if (internalformat == GL_ALPHA || format == GL_ALPHA) {
|
|
textureObject->SetSwizzleParamRGBA({TextureSwizzleParam::Zero, TextureSwizzleParam::Zero,
|
|
TextureSwizzleParam::Zero, TextureSwizzleParam::Red});
|
|
}
|
|
|
|
SizeT imageSize = 0;
|
|
const SizeT inputBpp = MG_Util::GetInputBytesPerPixel(textureInputFormat, texturePixelDataType);
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType);
|
|
const SizeT internalBytes = width * height * depth * internalBpp;
|
|
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
|
|
const void* originalPixels = pixels;
|
|
|
|
// PBO
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
MGLOG_D("%s: Using Pixel Unpack Buffer Object ID: %u", __func__,
|
|
pixelUnpackBufferObject->GetExternalIndex());
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
|
|
// Allocate in TextureObject
|
|
if (isProxy) {
|
|
MGLOG_D("%s: isProxy = true, not allocating", __func__);
|
|
} else {
|
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
|
|
}
|
|
|
|
if (!originalPixels) {
|
|
MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__);
|
|
return;
|
|
}
|
|
|
|
void* processedPixels = nullptr;
|
|
processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureInternalFormat,
|
|
textureInputFormat, texturePixelDataType, {width, height, depth}, false, imageSize);
|
|
|
|
if (processedPixels && imageSize > 0) {
|
|
if (imageSize != internalBytes) {
|
|
MGLOG_W("%s: Processed pixel data size (%zu) does not match expected size (%zu). "
|
|
"This may indicate an alignment or processing issue.",
|
|
__func__, imageSize, internalBytes);
|
|
}
|
|
|
|
const SizeT copySize = std::min(imageSize, internalBytes);
|
|
DataPtr texelInput{processedPixels, copySize};
|
|
textureMipmapObject->UpdateMipmapSubData(textureUploadTarget, level, texelInput);
|
|
}
|
|
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
|
|
|
|
free(processedPixels);
|
|
MaybeAutoGenerateMipmap(target, textureObject, isProxy, level);
|
|
}
|
|
|
|
void TexImage2D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border,
|
|
GLenum format, GLenum type, const void* pixels) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
MGLOG_D("%s called with target: %s (%s), level: %d, internalformat: %s (%s), width: %d, height: %d, "
|
|
"border: %d, format: %s (%s), type: %s (%s), pixels: %p",
|
|
__func__, MG_Util::ConvertTextureUploadTargetToString(textureUploadTarget).c_str(),
|
|
MG_Util::ConvertGLEnumToString(target).c_str(), level,
|
|
MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str(),
|
|
MG_Util::ConvertGLEnumToString(internalformat).c_str(), width, height, border,
|
|
MG_Util::ConvertTextureInputFormatToString(textureInputFormat).c_str(),
|
|
MG_Util::ConvertGLEnumToString(format).c_str(),
|
|
MG_Util::ConvertTexturePixelDataTypeToString(texturePixelDataType).c_str(),
|
|
MG_Util::ConvertGLEnumToString(type).c_str(), pixels);
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
|
if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat,
|
|
texturePixelDataType))
|
|
return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
// TODO: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the
|
|
// GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped.
|
|
// GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER
|
|
// target and the data would be unpacked from the buffer object such that the memory reads required would
|
|
// exceed the data store size.
|
|
// GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER
|
|
// target and data is not evenly divisible into the number of bytes needed to store in memory a datum
|
|
// indicated by type.
|
|
|
|
// ======================= Processing ================================
|
|
textureInternalFormat =
|
|
MG_Util::ConvertInternalFormatToSized(textureInternalFormat, textureInputFormat, texturePixelDataType);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
// ===================== Error Checking ==============================
|
|
// Name 0 resolves to the target's default texture object - a real texture this call
|
|
// (re)specifies like any other; the slot is never empty anymore.
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
// ======================= Processing ================================
|
|
if (internalformat == GL_ALPHA || format == GL_ALPHA) {
|
|
textureObject->SetSwizzleParamRGBA({TextureSwizzleParam::Zero, TextureSwizzleParam::Zero,
|
|
TextureSwizzleParam::Zero, TextureSwizzleParam::Red});
|
|
}
|
|
|
|
SizeT imageSize = 0;
|
|
const SizeT inputBpp = MG_Util::GetInputBytesPerPixel(textureInputFormat, texturePixelDataType);
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType);
|
|
const SizeT internalBytes = width * height * internalBpp;
|
|
|
|
MGLOG_D("%s: working on texture %d", __func__, textureObject->GetExternalIndex());
|
|
|
|
MGLOG_D("%s: texture object had internal format %s, new format %s", __func__,
|
|
MG_Util::ConvertTextureInternalFormatToString(textureObject->GetFormat()).c_str(),
|
|
MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str());
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
|
|
const void* originalPixels = pixels;
|
|
|
|
// PBO
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
MGLOG_D("%s: Using Pixel Unpack Buffer Object ID: %u", __func__,
|
|
pixelUnpackBufferObject->GetExternalIndex());
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
|
|
// Allocate in TextureObject
|
|
if (isProxy) {
|
|
MGLOG_D("%s: isProxy = true, not allocating", __func__);
|
|
} else {
|
|
MGLOG_D("%s: Allocating %d bytes at mip %d", __func__, internalBytes, level);
|
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
|
|
{{width, height, 1}, internalBytes});
|
|
}
|
|
|
|
if (!originalPixels) {
|
|
MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__);
|
|
return;
|
|
}
|
|
|
|
void* processedPixels = nullptr;
|
|
processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureInternalFormat,
|
|
textureInputFormat, texturePixelDataType, {width, height, 1}, false, imageSize);
|
|
|
|
if (processedPixels && imageSize > 0) {
|
|
if (imageSize != internalBytes) {
|
|
MGLOG_W("TexImage2D_State: Processed pixel data size (%zu) does not match expected size (%zu). "
|
|
"This may indicate an alignment or processing issue.",
|
|
imageSize, internalBytes);
|
|
}
|
|
|
|
const SizeT copySize = std::min(imageSize, internalBytes);
|
|
DataPtr texelInput{processedPixels, copySize};
|
|
textureMipmapObject->UpdateMipmapSubData(textureUploadTarget, level, texelInput);
|
|
}
|
|
|
|
free(processedPixels);
|
|
|
|
MGLOG_D("%s: mark mip %d as dirty", __func__, level);
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
|
|
MaybeAutoGenerateMipmap(target, textureObject, isProxy, level);
|
|
}
|
|
|
|
void TexImage1D_State(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format,
|
|
GLenum type, const GLvoid* pixels) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalFormat);
|
|
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, 1)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, 1, 1)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
|
if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat,
|
|
texturePixelDataType))
|
|
return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
textureInternalFormat =
|
|
MG_Util::ConvertInternalFormatToSized(textureInternalFormat, textureInputFormat, texturePixelDataType);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
// Name 0 resolves to the target's default texture object - a real texture this call
|
|
// (re)specifies like any other; the slot is never empty anymore.
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
if (internalFormat == GL_ALPHA || format == GL_ALPHA) {
|
|
textureObject->SetSwizzleParamRGBA({TextureSwizzleParam::Zero, TextureSwizzleParam::Zero,
|
|
TextureSwizzleParam::Zero, TextureSwizzleParam::Red});
|
|
}
|
|
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType);
|
|
const SizeT internalBytes = static_cast<SizeT>(width) * internalBpp;
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
|
|
const void* originalPixels = pixels;
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
if (!isProxy) {
|
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, 1, 1}, internalBytes});
|
|
}
|
|
|
|
if (!originalPixels) {
|
|
return;
|
|
}
|
|
|
|
SizeT imageSize = 0;
|
|
void* processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureInternalFormat,
|
|
textureInputFormat, texturePixelDataType, {width, 1, 1}, false, imageSize);
|
|
if (processedPixels && imageSize > 0) {
|
|
DataPtr texelInput{processedPixels, std::min(imageSize, internalBytes)};
|
|
textureMipmapObject->UpdateMipmapSubData(textureUploadTarget, level, texelInput);
|
|
}
|
|
|
|
free(processedPixels);
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
|
|
MaybeAutoGenerateMipmap(target, textureObject, isProxy, level);
|
|
}
|
|
|
|
// The work glTexBuffer[Range] and glTextureBuffer[Range] all share once the texture has been
|
|
// resolved - by binding for the target forms, by name for the DSA ones. `size` is
|
|
// kWholeBuffer for the non-Range entry points, which attach the buffer as it grows rather
|
|
// than freezing the size it happens to have now.
|
|
// The sized internal formats a buffer texture accepts (GL 4.6 core table 8.16). This is a much
|
|
// shorter list than the renderable or texturable formats, so it cannot be inferred from either.
|
|
Bool IsBufferTextureInternalFormat(GLenum internalformat) {
|
|
switch (internalformat) {
|
|
case GL_R8:
|
|
case GL_R16:
|
|
case GL_R16F:
|
|
case GL_R32F:
|
|
case GL_R8I:
|
|
case GL_R16I:
|
|
case GL_R32I:
|
|
case GL_R8UI:
|
|
case GL_R16UI:
|
|
case GL_R32UI:
|
|
case GL_RG8:
|
|
case GL_RG16:
|
|
case GL_RG16F:
|
|
case GL_RG32F:
|
|
case GL_RG8I:
|
|
case GL_RG16I:
|
|
case GL_RG32I:
|
|
case GL_RG8UI:
|
|
case GL_RG16UI:
|
|
case GL_RG32UI:
|
|
case GL_RGB32F:
|
|
case GL_RGB32I:
|
|
case GL_RGB32UI:
|
|
case GL_RGBA8:
|
|
case GL_RGBA16:
|
|
case GL_RGBA16F:
|
|
case GL_RGBA32F:
|
|
case GL_RGBA8I:
|
|
case GL_RGBA16I:
|
|
case GL_RGBA32I:
|
|
case GL_RGBA8UI:
|
|
case GL_RGBA16UI:
|
|
case GL_RGBA32UI:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size,
|
|
const char* caller) {
|
|
using MG_State::GLState::TextureObjectBuffer;
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
if (!IsBufferTextureInternalFormat(internalformat)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("internalformat 0x{:X} is not one of the sized formats a buffer texture accepts.",
|
|
internalformat)));
|
|
return;
|
|
}
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
|
|
|
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
|
if (buffer != 0 && !bufferObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"`buffer` is not zero and is not the name of an existing buffer object."));
|
|
return;
|
|
}
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
|
// A texture whose target is something else is a wrong object, not a wrong token
|
|
// (GL 4.6 core 8.9).
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"The effective target of `texture` is not `GL_TEXTURE_BUFFER`."));
|
|
return;
|
|
}
|
|
if (size != TextureObjectBuffer::kWholeBuffer) {
|
|
// GL 4.6 core 8.9: offset must be non-negative and aligned to
|
|
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, and size must be positive.
|
|
if (offset < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset must be non-negative."));
|
|
return;
|
|
}
|
|
if (size == 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "size must be greater than zero."));
|
|
return;
|
|
}
|
|
const Int alignment = std::max(
|
|
1, MG_Backend::pActiveBackendObject->GetDynamicParameters().TextureBufferOffsetAlignment);
|
|
if (offset % alignment != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"offset is not a multiple of GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT."));
|
|
return;
|
|
}
|
|
// The range has to lie inside the buffer that is being attached. Detaching (buffer
|
|
// zero) carries no range to check.
|
|
if (bufferObject && static_cast<SizeT>(offset) + size > bufferObject->GetSize()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"offset + size is greater than the buffer object's GL_BUFFER_SIZE."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto* texBufferObject = static_cast<TextureObjectBuffer*>(textureObject.get());
|
|
texBufferObject->GetBufferBindingSlot().Bind(bufferObject);
|
|
texBufferObject->SetBufferRange(static_cast<SizeT>(offset < 0 ? 0 : offset), size);
|
|
texBufferObject->SetInternalFormat(textureInternalFormat);
|
|
}
|
|
|
|
void TexBuffer_State(GLenum target, GLenum internalformat, GLuint buffer) {
|
|
// ======================= Converting ================================
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
|
// TODO: make sure `internalformat` is in one of supported format for TexBuffer
|
|
// GL 3.3 core 3.8.5: buffer zero detaches any buffer from the buffer texture - only a
|
|
// nonzero name that is not an existing buffer object is an error. This is reachable on
|
|
// the default buffer texture (bound whenever texture 0 is bound to GL_TEXTURE_BUFFER),
|
|
// which the GL CTS state reset detaches with glTexBuffer(..., 0) after every case.
|
|
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
|
if (buffer != 0 && !bufferObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"`buffer` is not zero and is not the name of an existing buffer object."));
|
|
return;
|
|
}
|
|
|
|
// ======================= Processing ================================
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
|
|
// ===================== Error Checking ==============================
|
|
// Name 0 is the default buffer texture - a real object the (de)attach operates on, not a
|
|
// silent no-op; the slot is never empty now that every unit/target holds its default.
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"The effective target of `texture` is not `GL_TEXTURE_BUFFER`."));
|
|
return;
|
|
}
|
|
|
|
// ======================= Processing ================================
|
|
// Now we can rest assured, and down-cast texture object to texture buffer
|
|
auto* texBufferObject = static_cast<MG_State::GLState::TextureObjectBuffer*>(textureObject.get());
|
|
auto& bufferSlot = texBufferObject->GetBufferBindingSlot();
|
|
bufferSlot.Bind(bufferObject);
|
|
|
|
texBufferObject->SetBufferRange(0, MG_State::GLState::TextureObjectBuffer::kWholeBuffer);
|
|
texBufferObject->SetInternalFormat(textureInternalFormat);
|
|
}
|
|
|
|
GLboolean IsTexture_State(GLuint texture) {
|
|
// ======================= Processing ================================
|
|
// GL 3.3 core 6.1.4: IsTexture generates no error - an unknown, deleted or merely reserved
|
|
// name is just GL_FALSE. Probing with the recording validator (as every other Is* entry
|
|
// point already avoids doing) would leave a spurious INVALID_VALUE behind.
|
|
return MG_State::pGLContext->ValidateTextureObject(texture) ? GL_TRUE : GL_FALSE;
|
|
}
|
|
|
|
void GetTexParameterIuiv_State(GLenum target, GLenum pname, GLuint* params) {
|
|
if (params == nullptr) return;
|
|
|
|
if (pname == GL_TEXTURE_BORDER_COLOR) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
|
|
const auto& borderColor = textureObject->GetBorderColorUI();
|
|
params[0] = borderColor.x();
|
|
params[1] = borderColor.y();
|
|
params[2] = borderColor.z();
|
|
params[3] = borderColor.w();
|
|
return;
|
|
}
|
|
|
|
GLint signedParams[4] = {0, 0, 0, 0};
|
|
if (!GetTexParameteriv_State(target, pname, signedParams)) return;
|
|
const int componentCount = pname == GL_TEXTURE_BORDER_COLOR || pname == GL_TEXTURE_SWIZZLE_RGBA ? 4 : 1;
|
|
for (int i = 0; i < componentCount; ++i) {
|
|
params[i] = static_cast<GLuint>(signedParams[i]);
|
|
}
|
|
}
|
|
|
|
void GetTexParameterIiv_State(GLenum target, GLenum pname, GLint* params) {
|
|
if (params == nullptr) return;
|
|
|
|
if (pname == GL_TEXTURE_BORDER_COLOR) {
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
|
|
const auto& borderColor = textureObject->GetBorderColorI();
|
|
params[0] = borderColor.x();
|
|
params[1] = borderColor.y();
|
|
params[2] = borderColor.z();
|
|
params[3] = borderColor.w();
|
|
return;
|
|
}
|
|
|
|
GetTexParameteriv_State(target, pname, params);
|
|
}
|
|
|
|
Bool GetTexParameteriv_State(GLenum target, GLenum pname, GLint* params) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return false;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetMagFilter(), SamplerMipmapMode::None);
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetMinFilter(),
|
|
textureObject->GetSamplerObject()->GetMipmapMode());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MIN_LOD:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMinLod());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MAX_LOD:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxLod());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_BASE_LEVEL:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetLevelRange().x());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MAX_LEVEL:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetLevelRange().y());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_IMMUTABLE_FORMAT:
|
|
if (params) {
|
|
*params = textureObject->IsImmutable() ? GL_TRUE : GL_FALSE;
|
|
}
|
|
break;
|
|
case GL_TEXTURE_IMMUTABLE_LEVELS:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetImmutableLevels());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_BORDER_COLOR:
|
|
if (params) {
|
|
const auto& borderColor = textureObject->GetBorderColor();
|
|
params[0] = static_cast<GLint>(borderColor.x());
|
|
params[1] = static_cast<GLint>(borderColor.y());
|
|
params[2] = static_cast<GLint>(borderColor.z());
|
|
params[3] = static_cast<GLint>(borderColor.w());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_RGBA:
|
|
if (params) {
|
|
const auto& swizzleParams = textureObject->GetAllSwizzleParams();
|
|
params[0] = static_cast<GLint>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[0]));
|
|
params[1] = static_cast<GLint>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[1]));
|
|
params[2] = static_cast<GLint>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[2]));
|
|
params[3] = static_cast<GLint>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[3]));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_R:
|
|
if (params) {
|
|
*params = static_cast<GLint>(
|
|
MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetSwizzleParam(TextureSwizzleParam::Red)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_G:
|
|
if (params) {
|
|
*params = static_cast<GLint>(MG_Util::ConvertTextureSwizzleParamToGLEnum(
|
|
textureObject->GetSwizzleParam(TextureSwizzleParam::Green)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_B:
|
|
if (params) {
|
|
*params = static_cast<GLint>(
|
|
MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetSwizzleParam(TextureSwizzleParam::Blue)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_A:
|
|
if (params) {
|
|
*params = static_cast<GLint>(MG_Util::ConvertTextureSwizzleParamToGLEnum(
|
|
textureObject->GetSwizzleParam(TextureSwizzleParam::Alpha)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_WRAP_S:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapS());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_WRAP_T:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapT());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_WRAP_R:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapR());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPARE_MODE:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerCompareModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetCompareMode());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPARE_FUNC:
|
|
if (params) {
|
|
*params = (GLint)MG_Util::ConvertSamplerCompareFuncToGLEnum(
|
|
textureObject->GetSamplerObject()->GetSamplerCompareFunc());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxAnisotropy());
|
|
}
|
|
break;
|
|
case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE:
|
|
if (params) {
|
|
*params = GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE;
|
|
}
|
|
break;
|
|
case GL_DEPTH_STENCIL_TEXTURE_MODE:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetDepthStencilTextureMode());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_LOD_BIAS:
|
|
if (params) {
|
|
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetLodBias());
|
|
}
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexParameteriv_State",
|
|
"pname is not a valid texture parameter."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GetTexParameterfv_State(GLenum target, GLenum pname, GLfloat* params) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ======================= Processing ================================
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_MAG_FILTER:
|
|
if (params) {
|
|
*params = (GLfloat)MG_Util::ConvertSamplerFilterModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetMagFilter(), SamplerMipmapMode::None);
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MIN_FILTER:
|
|
if (params) {
|
|
*params = (GLfloat)MG_Util::ConvertSamplerFilterModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetMinFilter(),
|
|
textureObject->GetSamplerObject()->GetMipmapMode());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MIN_LOD:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetSamplerObject()->GetMinLod());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MAX_LOD:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetSamplerObject()->GetMaxLod());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_BASE_LEVEL:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetLevelRange().x());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MAX_LEVEL:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetLevelRange().y());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_IMMUTABLE_FORMAT:
|
|
if (params) {
|
|
*params = textureObject->IsImmutable() ? 1.0f : 0.0f;
|
|
}
|
|
break;
|
|
case GL_TEXTURE_IMMUTABLE_LEVELS:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetImmutableLevels());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_BORDER_COLOR:
|
|
if (params) {
|
|
const auto& borderColor = textureObject->GetBorderColor();
|
|
params[0] = borderColor.x();
|
|
params[1] = borderColor.y();
|
|
params[2] = borderColor.z();
|
|
params[3] = borderColor.w();
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_R:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(
|
|
MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetSwizzleParam(TextureSwizzleParam::Red)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_G:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(
|
|
textureObject->GetSwizzleParam(TextureSwizzleParam::Green)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_B:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(
|
|
textureObject->GetSwizzleParam(TextureSwizzleParam::Blue)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_A:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(
|
|
textureObject->GetSwizzleParam(TextureSwizzleParam::Alpha)));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SWIZZLE_RGBA:
|
|
if (params) {
|
|
const auto& swizzleParams = textureObject->GetAllSwizzleParams();
|
|
params[0] = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[0]));
|
|
params[1] = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[1]));
|
|
params[2] = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[2]));
|
|
params[3] = static_cast<GLfloat>(MG_Util::ConvertTextureSwizzleParamToGLEnum(swizzleParams[3]));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_WRAP_S:
|
|
if (params) {
|
|
*params =
|
|
(GLfloat)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapS());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_WRAP_T:
|
|
if (params) {
|
|
*params =
|
|
(GLfloat)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapT());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_WRAP_R:
|
|
if (params) {
|
|
*params =
|
|
(GLfloat)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapR());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPARE_MODE:
|
|
if (params) {
|
|
*params = (GLfloat)MG_Util::ConvertSamplerCompareModeToGLEnum(
|
|
textureObject->GetSamplerObject()->GetCompareMode());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPARE_FUNC:
|
|
if (params) {
|
|
*params = (GLfloat)MG_Util::ConvertSamplerCompareFuncToGLEnum(
|
|
textureObject->GetSamplerObject()->GetSamplerCompareFunc());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
|
|
if (params) {
|
|
*params = textureObject->GetSamplerObject()->GetMaxAnisotropy();
|
|
}
|
|
break;
|
|
case GL_DEPTH_STENCIL_TEXTURE_MODE:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetDepthStencilTextureMode());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_LOD_BIAS:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetSamplerObject()->GetLodBias());
|
|
}
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexParameterfv_State",
|
|
"pname is not a valid texture parameter."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void GetTexLevelParameteriv_State(GLenum target, GLint level, GLenum pname, GLint* params) {
|
|
MGLOG_D("GetTexLevelParameteriv_State called");
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
// ======================= Processing ================================
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_WIDTH:
|
|
if (params) {
|
|
switch (textureObject->GetStorageType()) {
|
|
case TextureStorageType::Mipmap: {
|
|
const auto textureMipmapObject =
|
|
static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
*params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x();
|
|
break;
|
|
}
|
|
default:
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
}
|
|
break;
|
|
case GL_TEXTURE_HEIGHT:
|
|
if (params) {
|
|
switch (textureObject->GetStorageType()) {
|
|
case TextureStorageType::Mipmap: {
|
|
const auto textureMipmapObject =
|
|
static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
*params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y();
|
|
break;
|
|
}
|
|
default:
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
}
|
|
break;
|
|
case GL_TEXTURE_DEPTH:
|
|
if (params) {
|
|
switch (textureObject->GetStorageType()) {
|
|
case TextureStorageType::Mipmap: {
|
|
const auto textureMipmapObject =
|
|
static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
*params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z();
|
|
break;
|
|
}
|
|
default:
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
}
|
|
break;
|
|
case GL_TEXTURE_INTERNAL_FORMAT:
|
|
if (params) {
|
|
// A level stored compressed must report the token it was given, not the
|
|
// uncompressed format backing it (GL 4.6 core 8.11). Only glCompressedTexImage* sets
|
|
// that tag, so every level created by glTexImage*D - including one given a compressed
|
|
// internalformat - still answers with its resolved storage format.
|
|
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level);
|
|
*params = (compressedFormat != GL_NONE)
|
|
? (GLint)compressedFormat
|
|
: (GLint)MG_Util::ConvertTextureInternalFormatToGLEnum(textureObject->GetFormat());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SAMPLES:
|
|
if (params) {
|
|
*params = textureObject->GetSamples();
|
|
}
|
|
break;
|
|
case GL_TEXTURE_FIXED_SAMPLE_LOCATIONS:
|
|
if (params) {
|
|
*params = textureObject->HasFixedSampleLocations() ? GL_TRUE : GL_FALSE;
|
|
}
|
|
break;
|
|
case GL_TEXTURE_RED_TYPE:
|
|
case GL_TEXTURE_GREEN_TYPE:
|
|
case GL_TEXTURE_BLUE_TYPE:
|
|
case GL_TEXTURE_ALPHA_TYPE:
|
|
case GL_TEXTURE_DEPTH_TYPE:
|
|
case GL_TEXTURE_RED_SIZE:
|
|
case GL_TEXTURE_GREEN_SIZE:
|
|
case GL_TEXTURE_BLUE_SIZE:
|
|
case GL_TEXTURE_ALPHA_SIZE:
|
|
case GL_TEXTURE_DEPTH_SIZE:
|
|
case GL_TEXTURE_STENCIL_SIZE:
|
|
if (params) {
|
|
*params = GetTextureLevelComponentParameter(textureObject->GetFormat(), pname);
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPRESSED:
|
|
if (params) {
|
|
*params =
|
|
(GetCompressedLevelFormat(textureObject, textureUploadTarget, level) != GL_NONE) ? GL_TRUE
|
|
: GL_FALSE;
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPRESSED_IMAGE_SIZE: {
|
|
// GL 4.6 core 8.11: there is no compressed size to report for an image whose internal
|
|
// format is uncompressed, nor for a proxy target, and the query is INVALID_OPERATION
|
|
// rather than a zero.
|
|
if (isProxy || GetCompressedLevelFormat(textureObject, textureUploadTarget, level) == GL_NONE) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "GetTexLevelParameteriv_State",
|
|
"GL_TEXTURE_COMPRESSED_IMAGE_SIZE needs a compressed, non-proxy texture image."));
|
|
return;
|
|
}
|
|
if (params) {
|
|
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
|
*params = static_cast<GLint>(
|
|
textureMipmapObject->GetMipmapCompressedByteSize(textureUploadTarget, static_cast<Uint>(level)));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexLevelParameteriv_State",
|
|
"pname is not a valid texture level parameter."));
|
|
return;
|
|
}
|
|
if (params) MGLOG_D("returned %u", *params);
|
|
}
|
|
|
|
void GetTexLevelParameterfv_State(GLenum target, GLint level, GLenum pname, GLfloat* params) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
|
|
// ======================= Processing ================================
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
|
|
switch (pname) {
|
|
case GL_TEXTURE_WIDTH:
|
|
if (params) {
|
|
switch (textureObject->GetStorageType()) {
|
|
case TextureStorageType::Mipmap: {
|
|
const auto textureMipmapObject =
|
|
static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
*params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x();
|
|
break;
|
|
}
|
|
default:
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
}
|
|
break;
|
|
case GL_TEXTURE_HEIGHT:
|
|
if (params) {
|
|
switch (textureObject->GetStorageType()) {
|
|
case TextureStorageType::Mipmap: {
|
|
const auto textureMipmapObject =
|
|
static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
*params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y();
|
|
break;
|
|
}
|
|
default:
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
}
|
|
break;
|
|
case GL_TEXTURE_DEPTH:
|
|
if (params) {
|
|
switch (textureObject->GetStorageType()) {
|
|
case TextureStorageType::Mipmap: {
|
|
const auto textureMipmapObject =
|
|
static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
*params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z();
|
|
break;
|
|
}
|
|
default:
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
}
|
|
break;
|
|
case GL_TEXTURE_INTERNAL_FORMAT:
|
|
if (params) {
|
|
// A level stored compressed must report the token it was given, not the
|
|
// uncompressed format backing it (GL 4.6 core 8.11). Only glCompressedTexImage* sets
|
|
// that tag, so every level created by glTexImage*D - including one given a compressed
|
|
// internalformat - still answers with its resolved storage format.
|
|
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level);
|
|
*params = (GLfloat)((compressedFormat != GL_NONE)
|
|
? compressedFormat
|
|
: MG_Util::ConvertTextureInternalFormatToGLEnum(textureObject->GetFormat()));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_SAMPLES:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(textureObject->GetSamples());
|
|
}
|
|
break;
|
|
case GL_TEXTURE_FIXED_SAMPLE_LOCATIONS:
|
|
if (params) {
|
|
*params = textureObject->HasFixedSampleLocations() ? 1.0f : 0.0f;
|
|
}
|
|
break;
|
|
case GL_TEXTURE_RED_TYPE:
|
|
case GL_TEXTURE_GREEN_TYPE:
|
|
case GL_TEXTURE_BLUE_TYPE:
|
|
case GL_TEXTURE_ALPHA_TYPE:
|
|
case GL_TEXTURE_DEPTH_TYPE:
|
|
case GL_TEXTURE_RED_SIZE:
|
|
case GL_TEXTURE_GREEN_SIZE:
|
|
case GL_TEXTURE_BLUE_SIZE:
|
|
case GL_TEXTURE_ALPHA_SIZE:
|
|
case GL_TEXTURE_DEPTH_SIZE:
|
|
case GL_TEXTURE_STENCIL_SIZE:
|
|
if (params) {
|
|
*params = static_cast<GLfloat>(GetTextureLevelComponentParameter(textureObject->GetFormat(), pname));
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPRESSED:
|
|
if (params) {
|
|
*params =
|
|
(GetCompressedLevelFormat(textureObject, textureUploadTarget, level) != GL_NONE) ? 1.0f : 0.0f;
|
|
}
|
|
break;
|
|
case GL_TEXTURE_COMPRESSED_IMAGE_SIZE: {
|
|
// See GetTexLevelParameteriv_State: uncompressed images and proxy targets have no
|
|
// compressed size to report, so GL 4.6 core 8.11 makes the query an error.
|
|
if (isProxy || GetCompressedLevelFormat(textureObject, textureUploadTarget, level) == GL_NONE) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "GetTexLevelParameterfv_State",
|
|
"GL_TEXTURE_COMPRESSED_IMAGE_SIZE needs a compressed, non-proxy texture image."));
|
|
return;
|
|
}
|
|
if (params) {
|
|
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
|
*params = static_cast<GLfloat>(
|
|
textureMipmapObject->GetMipmapCompressedByteSize(textureUploadTarget, static_cast<Uint>(level)));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexLevelParameterfv_State",
|
|
"pname is not a valid texture level parameter."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// The half glGetCompressedTexImage and glGetCompressedTextureImage share, factored out for the
|
|
// same reason ValidateTextureImageQuery was: the by-name entry point must not drift away from
|
|
// the by-target one's rules. bufSize < 0 means "no destination-size argument" - the by-target
|
|
// form has none (GL 4.6 core 8.11 has the caller size it from GL_TEXTURE_COMPRESSED_IMAGE_SIZE),
|
|
// so only the DSA form passes a real bound.
|
|
void CopyCompressedTextureImageToClientOrPBO(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
TextureUploadTarget uploadTarget, GLint level, GLsizei bufSize,
|
|
void* pixels, const char* caller) {
|
|
if (GetCompressedLevelFormat(textureObject, uploadTarget, level) == GL_NONE) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Texture level is not stored in a compressed format."));
|
|
return;
|
|
}
|
|
|
|
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
|
const SizeT imageSize =
|
|
textureMipmapObject->GetMipmapCompressedByteSize(uploadTarget, static_cast<Uint>(level));
|
|
const void* src = textureMipmapObject->MapMipmapCompressedImage(uploadTarget, static_cast<Uint>(level));
|
|
if (!src || imageSize == 0) return;
|
|
|
|
if (bufSize >= 0 && static_cast<SizeT>(bufSize) < imageSize) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Destination buffer is too small."));
|
|
return;
|
|
}
|
|
|
|
const auto& pixelPackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
|
if (pixelPackBufferObject) {
|
|
if (pixelPackBufferObject->IsMapped()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Pixel pack buffer is currently mapped."));
|
|
return;
|
|
}
|
|
const SizeT offset = reinterpret_cast<SizeT>(pixels);
|
|
const SizeT bufferSize = pixelPackBufferObject->GetSize();
|
|
if (offset > bufferSize || imageSize > bufferSize - offset) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Packing would write past the end of the pixel pack buffer."));
|
|
return;
|
|
}
|
|
pixelPackBufferObject->UploadSubData({const_cast<void*>(src), imageSize}, offset);
|
|
return;
|
|
}
|
|
|
|
// No pixel-store packing here on purpose: GL 4.6 core 8.11 says the pixel storage modes are
|
|
// ignored for a compressed image, which is also the only way the round trip stays byte-exact.
|
|
if (pixels) Memcpy(pixels, src, imageSize);
|
|
}
|
|
|
|
void GetCompressedTexImage_State(GLenum target, GLint level, void* img) {
|
|
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
// ValidateTextureUploadTarget records InvalidEnum itself; wrapping it in a second RecordError
|
|
// would report one failure twice.
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
CopyCompressedTextureImageToClientOrPBO(textureObject, textureUploadTarget, level, -1, img, __func__);
|
|
}
|
|
|
|
void GenTextures_State(GLsizei n, GLuint* textures) {
|
|
// ===================== Error Checking ==============================
|
|
if (n < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GenTextures_State", "n must be non-negative"));
|
|
return;
|
|
}
|
|
|
|
// ======================= Processing ================================
|
|
Vector<Uint> textureNames;
|
|
MG_State::pGLContext->GenTextureNames(n, textureNames);
|
|
Memcpy(textures, textureNames.data(), n * sizeof(GLuint));
|
|
}
|
|
|
|
void DeleteTextures_State(GLsizei n, const GLuint* textures) {
|
|
// ===================== Error Checking ==============================
|
|
if (n < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "DeleteTextures_State", "n must be non-negative."));
|
|
return;
|
|
}
|
|
|
|
if (!textures) {
|
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "DeleteTextures_State",
|
|
"Texture names array cannot be null."));
|
|
return;
|
|
}
|
|
|
|
// ======================= Processing ================================
|
|
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
|
|
Uint textureName = textures[i];
|
|
if (textureName == 0) continue;
|
|
if (!MG_State::pGLContext->ValidateTextureName(textureName)) continue;
|
|
MG_State::pGLContext->MarkTextureObjectForDeletion(textureName);
|
|
}
|
|
}
|
|
|
|
void CopyTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x,
|
|
GLint y, GLsizei width, GLsizei height) {
|
|
// TODO: implement
|
|
}
|
|
|
|
// What the three CopyTextureSubImage forms check in common (GL 4.6 core 8.6), once the caller
|
|
// has rejected an effective target its own form does not accept: the destination region has to
|
|
// lie inside the level, and the read framebuffer has to be able to supply pixels at all.
|
|
Bool ValidateCopyTextureSubImage(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLint level,
|
|
GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height,
|
|
GLsizei depth, const char* caller) {
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return false;
|
|
if (width < 0 || height < 0 || depth < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Copy dimensions must be non-negative."));
|
|
return false;
|
|
}
|
|
if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height, zoffset,
|
|
depth)) {
|
|
return false;
|
|
}
|
|
return FramebufferImpl::ValidateReadFramebufferForCopy(caller);
|
|
}
|
|
|
|
void CopyTexSubImage2D_Backend(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
|
GLsizei width, GLsizei height) {
|
|
MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
|
}
|
|
|
|
void CopyImageSubData_Backend(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
|
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
|
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
|
auto copyImageSubData = MG_Backend::gBackendFunctionsTable.GL.CopyImageSubData;
|
|
if (!copyImageSubData) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Backend does not support image-to-image copies."));
|
|
return;
|
|
}
|
|
copyImageSubData(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, dstTexture, dstTarget, dstLevel, dstX,
|
|
dstY, dstZ, srcWidth, srcHeight, srcDepth);
|
|
}
|
|
|
|
Bool ValidateCopyImageSubData_State(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
|
GLenum srcTarget, GLint srcLevel,
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
|
GLenum dstTarget, GLint dstLevel,
|
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
|
if (!TextureImpl::ValidateTextureObject(srcTexture) || !TextureImpl::ValidateTextureObject(dstTexture)) {
|
|
return false;
|
|
}
|
|
const auto srcTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(srcTarget);
|
|
const auto dstTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(dstTarget);
|
|
if (!TextureImpl::ValidateTextureTarget(srcTextureTarget) ||
|
|
!TextureImpl::ValidateTextureTarget(dstTextureTarget)) {
|
|
return false;
|
|
}
|
|
if (!TextureImpl::ValidateTextureTargetUniformity(srcTexture, srcTextureTarget) ||
|
|
!TextureImpl::ValidateTextureTargetUniformity(dstTexture, dstTextureTarget)) {
|
|
return false;
|
|
}
|
|
if (!TextureImpl::ValidateTextureLevelNumber(srcLevel) ||
|
|
!TextureImpl::ValidateTextureLevelNumber(dstLevel)) {
|
|
return false;
|
|
}
|
|
if (srcWidth < 0 || srcHeight < 0 || srcDepth < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Copy dimensions must be non-negative."));
|
|
return false;
|
|
}
|
|
if (srcWidth == 0 || srcHeight == 0 || srcDepth == 0) {
|
|
return false;
|
|
}
|
|
if (!TextureImpl::ValidateBaseInternalFormatMatch(srcTexture->GetFormat(), dstTexture->GetFormat())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CopyTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) {
|
|
// TODO: implement
|
|
}
|
|
|
|
Bool CopyTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
|
GLsizei height, GLint border) {
|
|
auto internalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return false;
|
|
|
|
const auto& currentReadFBO =
|
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
|
|
if (!currentReadFBO) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "CopyTexImage2D_State",
|
|
"No framebuffer is currently bound to the GL_READ_FRAMEBUFFER target."));
|
|
return false;
|
|
}
|
|
|
|
Bool isDepth = MG_Util::IsDepthFormatInternalFormat(internalFormat);
|
|
Bool isStencil = MG_Util::IsStencilFormatInternalFormat(internalFormat);
|
|
TextureInternalFormat srcInternalFormat = TextureInternalFormat::Unknown;
|
|
#define GET_SRC_INTERNAL_FORMAT(AttachmentType) \
|
|
const auto& srcAttachment = currentReadFBO->GetAttachment(AttachmentType); \
|
|
if (srcAttachment.IsTexture()) { \
|
|
const auto& texObj = srcAttachment.GetTexture(); \
|
|
srcInternalFormat = texObj->GetFormat(); \
|
|
} else if (srcAttachment.IsRenderbuffer()) { \
|
|
const auto& rboObj = srcAttachment.GetRenderbuffer(); \
|
|
srcInternalFormat = rboObj->GetInternalFormat(); \
|
|
} else { \
|
|
MG_State::pGLContext->RecordError( \
|
|
ErrorCode::InvalidOperation, \
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "CopyTexImage2D_State", \
|
|
"The attachment specified by the read buffer is incomplete.")); \
|
|
return false; \
|
|
}
|
|
if (isDepth && isStencil) {
|
|
// A combined internalformat copies both halves, so the read framebuffer
|
|
// must populate both attachment points.
|
|
const auto& stencilAttachment = currentReadFBO->GetAttachment(FramebufferAttachmentType::Stencil);
|
|
const auto& depthAttachment = currentReadFBO->GetAttachment(FramebufferAttachmentType::Depth);
|
|
if (!depthAttachment.IsValid() || depthAttachment.IsEmpty() || !stencilAttachment.IsValid() ||
|
|
stencilAttachment.IsEmpty()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "CopyTexImage2D_State",
|
|
"DEPTH_STENCIL copy requires both depth and stencil attachments in the read framebuffer."));
|
|
return false;
|
|
}
|
|
GET_SRC_INTERNAL_FORMAT(FramebufferAttachmentType::Depth);
|
|
} else if (isDepth) {
|
|
GET_SRC_INTERNAL_FORMAT(FramebufferAttachmentType::Depth);
|
|
} else if (isStencil) {
|
|
GET_SRC_INTERNAL_FORMAT(FramebufferAttachmentType::Stencil);
|
|
} else {
|
|
const auto& readBufferType = currentReadFBO->GetReadBuffer();
|
|
GET_SRC_INTERNAL_FORMAT(readBufferType);
|
|
}
|
|
|
|
if (!TextureImpl::ValidateBaseInternalFormatMatch(internalFormat, srcInternalFormat)) THROW_UNIMPL_EXCEPTION;
|
|
|
|
GLenum outInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(srcInternalFormat);
|
|
GLenum realInternalFormat = GL_RGBA8;
|
|
GLenum format = GL_DEPTH_COMPONENT;
|
|
GLenum type = GL_UNSIGNED_INT;
|
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(outInternalFormat, PixelFormatNormalizeOptionBit::None,
|
|
&realInternalFormat, &format, &type);
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
TexImage2D_State(target, level, (GLint)realInternalFormat, width, height, border, format, type, nullptr);
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).Bind(pixelUnpackBufferObject);
|
|
return true;
|
|
}
|
|
|
|
void CopyTexImage2D_Backend(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
|
GLsizei height, GLint border) {
|
|
MG_Backend::gBackendFunctionsTable.GL.CopyTexImage2D(target, level, internalformat, x, y, width, height,
|
|
border);
|
|
}
|
|
|
|
void CopyTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
|
GLint border) {
|
|
// TODO: implement
|
|
THROW_UNIMPL_EXCEPTION;
|
|
}
|
|
|
|
void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
|
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize,
|
|
const void* data) {
|
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
|
RecordUnsupportedCompressedFormat(__func__);
|
|
}
|
|
|
|
void CompressedTexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
|
GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
|
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
|
RecordUnsupportedCompressedFormat(__func__);
|
|
}
|
|
|
|
void CompressedTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format,
|
|
GLsizei imageSize, const void* data) {
|
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
|
RecordUnsupportedCompressedFormat(__func__);
|
|
}
|
|
|
|
void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLsizei depth, GLint border, GLsizei imageSize, const void* data) {
|
|
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
|
RecordUnsupportedCompressedFormat(__func__);
|
|
}
|
|
|
|
void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLint border, GLsizei imageSize, const void* data) {
|
|
// ======================= Converting ================================
|
|
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
// Zero block width doubles as "internalformat is not a specific compressed format", which is
|
|
// the INVALID_ENUM case - one lookup answers both questions.
|
|
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(internalformat);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return;
|
|
if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
|
|
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
|
|
if (compressedInfo.blockWidth == 0) {
|
|
RecordUnsupportedCompressedFormat(__func__);
|
|
return;
|
|
}
|
|
// GL 4.6 core 8.7: imageSize must be exactly the size the format and dimensions imply,
|
|
// otherwise INVALID_VALUE. This is also the guard that keeps the copy below in bounds.
|
|
const SizeT expectedImageSize =
|
|
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, 1});
|
|
if (imageSize < 0 || static_cast<SizeT>(imageSize) != expectedImageSize) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"imageSize does not match the compressed image size."));
|
|
return;
|
|
}
|
|
|
|
// Object resolution copied from TexImage2D_State rather than routed through
|
|
// GetTextureObjectByTarget: GL 4.6 core 8.7 lets a proxy target reach glCompressedTexImage2D,
|
|
// and only CreateOrReplaceProxyTextureObject gives the proxy a fresh object to answer the
|
|
// level queries from.
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
const Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
// ======================= Processing ================================
|
|
// Texel storage stays uncompressed, exactly the deviation the RGTC/BPTC/ETC2 arms of
|
|
// ConvertGLEnumToTextureInternalFormat already document: neither backend has a BC/ETC codec
|
|
// and TextureInternalFormat has no compressed enumerator, so the shadow keeps the "one
|
|
// format, N bytes per texel" layout the backend upload sizing, glGenerateMipmap's
|
|
// bytes-per-texel division and the pixel-store packer all rely on. The image therefore
|
|
// samples as zeros. The application's bytes are kept beside it so glGetCompressedTexImage can
|
|
// return the image *as stored*, which GL 4.6 core 8.11 requires and which no re-encode could
|
|
// satisfy byte for byte.
|
|
const TextureInternalFormat textureInternalFormat =
|
|
MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
|
|
// A proxy records the format and nothing else - it must never take storage, and it must never
|
|
// be tagged compressed, or GL_TEXTURE_COMPRESSED_IMAGE_SIZE on a proxy would stop being
|
|
// INVALID_OPERATION.
|
|
if (isProxy) return;
|
|
|
|
const SizeT internalBpp =
|
|
MG_Util::GetInternalBytesPerPixel(textureInternalFormat, TexturePixelDataType::UnsignedByte);
|
|
const SizeT internalBytes = static_cast<SizeT>(width) * static_cast<SizeT>(height) * internalBpp;
|
|
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
|
// AllocateStorage clears any compressed image the level used to hold, so this must run before
|
|
// SetMipmapCompressedImage re-arms it.
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, 1}, internalBytes});
|
|
|
|
const void* compressedBytes = data;
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
if (pixelUnpackBufferObject->IsMapped()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Pixel unpack buffer is currently mapped."));
|
|
return;
|
|
}
|
|
const SizeT offset = reinterpret_cast<SizeT>(data);
|
|
const SizeT bufferSize = pixelUnpackBufferObject->GetSize();
|
|
if (offset > bufferSize || expectedImageSize > bufferSize - offset) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Unpacking would read past the end of the pixel unpack buffer."));
|
|
return;
|
|
}
|
|
compressedBytes = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) + offset;
|
|
}
|
|
textureMipmapObject->SetMipmapCompressedImage(textureUploadTarget, level, internalformat, compressedBytes,
|
|
expectedImageSize);
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
|
|
}
|
|
|
|
void CompressedTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border,
|
|
GLsizei imageSize, const void* data) {
|
|
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
|
RecordUnsupportedCompressedFormat(__func__);
|
|
}
|
|
|
|
void BindTexture_State(GLenum target, GLuint texture) {
|
|
const Int activeUnit = MG_State::pGLContext->GetActiveTextureUnit();
|
|
MGLOG_D("BindTexture_State called with target: 0x%X, texture: %u, unit: %d", target, texture, activeUnit);
|
|
// ======================= Converting ================================
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
|
|
|
|
// GL 3.3 core 3.8: name 0 is the target's default texture object - a real texture that
|
|
// glTexImage*/glTexParameter*/glGetTex* must operate on - not "nothing bound". Binding it
|
|
// restores the unit/target slot to its initial state.
|
|
if (texture == 0) {
|
|
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(activeUnit);
|
|
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
|
|
const Bool changed = bindingSlot.Bind(MG_State::pGLContext->GetDefaultTextureObject(textureTarget));
|
|
MG_State::pGLContext->NoteTextureUnitTouched(activeUnit, changed);
|
|
return;
|
|
}
|
|
|
|
// Some desktop-side helper code saves GL_ACTIVE_TEXTURE and later feeds it back into glBindTexture
|
|
// as if it were a texture name. Treating that as a no-op preserves the previous "invalid bind does not
|
|
// change texture state" behavior, but avoids poisoning the error state every frame.
|
|
if (!MG_State::pGLContext->ValidateTextureName(texture) && texture >= GL_TEXTURE0 && texture <= GL_TEXTURE31) {
|
|
return;
|
|
}
|
|
|
|
// GL 3.3 core 3.8.1: a name that GenTextures never returned - or that has since been deleted -
|
|
// is not a legal bind target in the core profile (no application-generated names), and the error
|
|
// is INVALID_OPERATION, not INVALID_VALUE.
|
|
if (!MG_State::pGLContext->ValidateTextureName(texture)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "BindTexture_State", "Invalid texture name"));
|
|
return;
|
|
}
|
|
|
|
// ======================= Processing ================================
|
|
Bool doesTextureExist = MG_State::pGLContext->ValidateTextureObject(texture);
|
|
if (!doesTextureExist) {
|
|
MG_State::pGLContext->CreateTextureObject(texture, textureTarget);
|
|
}
|
|
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
|
|
|
// ===================== Error Checking ==============================
|
|
if (doesTextureExist && !TextureImpl::ValidateTextureTargetUniformity(textureObject, textureTarget)) return;
|
|
|
|
// ======================= Processing ================================
|
|
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
|
|
const Bool changed = bindingSlot.Bind(textureObject);
|
|
MG_State::pGLContext->NoteTextureUnitTouched(MG_State::pGLContext->GetActiveTextureUnit(), changed);
|
|
}
|
|
|
|
void ActiveTexture_State(GLenum texture) {
|
|
// ===================== Error Checking ==============================
|
|
// GL 3.3 core 3.8: the valid range is [GL_TEXTURE0, GL_TEXTURE0 +
|
|
// GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) - NOT a fixed 0..31 range. GL CTS's per-case
|
|
// state reset iterates every advertised combined unit, so rejecting units the
|
|
// implementation itself reports would leave a sticky GL_INVALID_ENUM behind and abort
|
|
// whole test batches. The backend already clamps its advertised value to the state
|
|
// layer's MAX_TEXTURE_IMAGE_UNITS capacity.
|
|
Int maxCombinedUnits = static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
|
if (MG_Backend::pActiveBackendObject) {
|
|
maxCombinedUnits = std::min(
|
|
maxCombinedUnits, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxCombinedTextureImageUnits);
|
|
}
|
|
if (texture < GL_TEXTURE0 || static_cast<Int>(texture - GL_TEXTURE0) >= maxCombinedUnits) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "ActiveTexture_State",
|
|
std::format("Texture must be one of GL_TEXTUREi, where i is in the range 0 to {}, but got "
|
|
"invalid enum: 0x{:X}, which may stand for unit {}.",
|
|
maxCombinedUnits - 1, texture, texture - GL_TEXTURE0)));
|
|
return;
|
|
}
|
|
|
|
// ======================= Processing ================================
|
|
const Int unit = (Int)texture - GL_TEXTURE0;
|
|
MGLOG_D("ActiveTexture_State: unit = %d", unit);
|
|
MG_State::pGLContext->SetActiveTextureUnit(unit);
|
|
}
|
|
|
|
void GetTexImage_Backend(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
|
|
MG_Backend::gBackendFunctionsTable.GL.GetTexImage(target, level, format, type, pixels);
|
|
}
|
|
|
|
// Add to GL_Texture.cpp
|
|
// The half of the GetTexImage/GetTextureImage error set (GL 4.6 core 8.11) that depends on the
|
|
// resolved texture object rather than on how it was named. Shared because the by-name entry
|
|
// point does not route through GetTexImage_State and so used to enforce none of it.
|
|
Bool ValidateTextureImageQuery(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLint level,
|
|
TextureInputFormat textureInputFormat, TexturePixelDataType texturePixelDataType,
|
|
GLsizei bufSize, const void* pixels, const char* caller) {
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "No valid texture bound to target"));
|
|
return false;
|
|
}
|
|
|
|
// A multisample texture has per-sample data with no single image to return, and a buffer
|
|
// texture's data lives in the buffer object - neither target is in the accepted list.
|
|
const auto target = textureObject->GetTarget();
|
|
if (target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray ||
|
|
target == TextureTarget::TextureBuffer) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Texture target has no image to read back."));
|
|
return false;
|
|
}
|
|
|
|
// Level range. The by-target path would reach these again inside
|
|
// CopyTextureImageToClientOrPBO_State, but the by-name path on a backend that answers
|
|
// GetTextureImage itself never gets there.
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return false;
|
|
if (target == TextureTarget::TextureRectangle && level != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Level must be zero for rectangle textures"));
|
|
return false;
|
|
}
|
|
|
|
// For a cube map this is exactly cube completeness: IsComplete() wants all six faces.
|
|
if (!textureObject->IsComplete()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture is incomplete"));
|
|
return false;
|
|
}
|
|
|
|
// Check PBO state
|
|
const auto& pixelPackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
|
|
|
if (pixelPackBufferObject) {
|
|
// Check if PBO is mapped
|
|
if (pixelPackBufferObject->IsMapped()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Pixel pack buffer is currently mapped"));
|
|
return false;
|
|
}
|
|
|
|
// Check alignment
|
|
const SizeT typeSize = MG_Util::GetTexturePixelDataTypeSize(texturePixelDataType);
|
|
if (typeSize != 0 && reinterpret_cast<uintptr_t>(pixels) % typeSize != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Pixel data not aligned for pixel pack buffer"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Shared format/type/internal-format matrix (packed-type pairing, depth-vs-color mismatch,
|
|
// integer-ness). Also rejects STENCIL_INDEX readback, which needs GL_ARB_texture_stencil8
|
|
// (not advertised by MobileGL).
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(
|
|
textureInputFormat, textureObject->GetFormat(), texturePixelDataType)) {
|
|
return false;
|
|
}
|
|
|
|
// GetTexImage-specific: DEPTH_STENCIL readback needs a depth-stencil texture (a depth-only
|
|
// texture has no stencil data to return).
|
|
if (textureInputFormat == TextureInputFormat::DepthStencil &&
|
|
textureObject->GetFormat() != TextureInternalFormat::DepthStencil &&
|
|
textureObject->GetFormat() != TextureInternalFormat::Depth24Stencil8 &&
|
|
textureObject->GetFormat() != TextureInternalFormat::Depth32FStencil8) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"DEPTH_STENCIL readback requires a depth-stencil texture"));
|
|
return false;
|
|
}
|
|
|
|
// The destination has to be big enough. This has to happen here rather than after the read
|
|
// has been packed: any of the reasons the read can bail out early - an unmapped level, a
|
|
// pack step that declines the format - would otherwise swallow the error entirely.
|
|
if (textureObject->GetStorageType() == TextureStorageType::Mipmap) {
|
|
const auto* textureMipmapObject =
|
|
static_cast<const MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
const auto& uploadTargets = textureObject->GetUploadTargets();
|
|
if (!uploadTargets.empty() && static_cast<Uint>(level) < textureMipmapObject->GetMipmapLevelCount()) {
|
|
// Tightly packed, and summed over every face because a cube map query returns all
|
|
// six. Pack pixel-store state only ever grows this, so a request rejected here
|
|
// could not have fit under any packing.
|
|
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTargets[0], level);
|
|
const SizeT required = MG_Util::CalculateInputTextureImageSize(textureInputFormat,
|
|
texturePixelDataType, texelSize) *
|
|
uploadTargets.size();
|
|
|
|
if (bufSize >= 0 && static_cast<SizeT>(bufSize) < required) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Destination buffer is too small."));
|
|
return false;
|
|
}
|
|
|
|
if (pixelPackBufferObject) {
|
|
const SizeT bufferSize = pixelPackBufferObject->GetSize();
|
|
const SizeT offset = reinterpret_cast<SizeT>(pixels);
|
|
if (offset > bufferSize || required > bufferSize - offset) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Packing would write past the end of the pixel pack buffer."));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Bool GetTexImage_State(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
|
|
// ======================= Converting ================================
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
|
|
// ===================== Error Checking ==============================
|
|
// Validate target
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexImage_State", "Invalid texture target"));
|
|
return false;
|
|
}
|
|
|
|
// Validate level
|
|
if (level < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexImage_State", "Level must be non-negative"));
|
|
return false;
|
|
}
|
|
|
|
// Validate format
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexImage_State", "Invalid format"));
|
|
return false;
|
|
}
|
|
|
|
// Validate type
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexImage_State", "Invalid pixel data type"));
|
|
return false;
|
|
}
|
|
|
|
// Get texture object
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
|
|
auto& textureObject =
|
|
isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget)
|
|
: bindingSlot.GetBoundObject();
|
|
|
|
// glGetTexImage has no bufSize argument: -1 stands for "no client-side limit".
|
|
return ValidateTextureImageQuery(textureObject, level, textureInputFormat, texturePixelDataType, -1, pixels,
|
|
"GetTexImage_State");
|
|
}
|
|
|
|
void CopyTextureImageToClientOrPBO_State(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
TextureUploadTarget textureUploadTarget, GLint level, GLenum format,
|
|
GLenum type, GLsizei bufSize, void* pixels, const char* caller) {
|
|
if (!textureObject) return;
|
|
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture storage is not mipmap-backed."));
|
|
return;
|
|
}
|
|
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture level is out of range."));
|
|
return;
|
|
}
|
|
|
|
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level);
|
|
const void* src = textureMipmapObject->MapMipmapData(textureUploadTarget, level);
|
|
if (!src) return;
|
|
|
|
SizeT packedSize = 0;
|
|
void* packedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataPack(
|
|
src, MG_State::pGLContext->GetPixelStoreParameters(false), textureObject->GetFormat(), texturePixelDataType,
|
|
textureInputFormat, texturePixelDataType, texelSize, false, packedSize);
|
|
if (!packedPixels || packedSize == 0) {
|
|
if (packedPixels) free(packedPixels);
|
|
return;
|
|
}
|
|
|
|
if (bufSize >= 0 && static_cast<SizeT>(bufSize) < packedSize) {
|
|
free(packedPixels);
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Destination buffer is too small."));
|
|
return;
|
|
}
|
|
|
|
const auto& pixelPackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
|
if (pixelPackBufferObject) {
|
|
const SizeT offset = reinterpret_cast<SizeT>(pixels);
|
|
if (offset + packedSize > pixelPackBufferObject->GetSize()) {
|
|
free(packedPixels);
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Pixel pack buffer is too small."));
|
|
return;
|
|
}
|
|
pixelPackBufferObject->UploadSubData({packedPixels, packedSize}, offset);
|
|
} else if (pixels) {
|
|
Memcpy(pixels, packedPixels, packedSize);
|
|
}
|
|
|
|
free(packedPixels);
|
|
}
|
|
|
|
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
|
void CreateTextures(GLenum target, GLsizei n, GLuint* textures) {
|
|
if (n < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
|
return;
|
|
}
|
|
if (n > 0 && !textures) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture output pointer cannot be null."));
|
|
return;
|
|
}
|
|
|
|
switch (target) {
|
|
case GL_TEXTURE_1D:
|
|
case GL_TEXTURE_2D:
|
|
case GL_TEXTURE_3D:
|
|
case GL_TEXTURE_1D_ARRAY:
|
|
case GL_TEXTURE_2D_ARRAY:
|
|
case GL_TEXTURE_RECTANGLE:
|
|
case GL_TEXTURE_CUBE_MAP:
|
|
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
|
case GL_TEXTURE_BUFFER:
|
|
case GL_TEXTURE_2D_MULTISAMPLE:
|
|
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Invalid texture target."));
|
|
return;
|
|
}
|
|
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
|
|
|
|
Vector<Uint> textureNames;
|
|
MG_State::pGLContext->GenTextureNames(n, textureNames);
|
|
for (GLsizei i = 0; i < n; ++i) {
|
|
textures[i] = textureNames[i];
|
|
MG_State::pGLContext->CreateTextureObject(textureNames[i], textureTarget);
|
|
}
|
|
}
|
|
|
|
// Shared front half of glTextureStorage1D/2D/3D. `dimension` selects which set of targets the
|
|
// entry point accepts; `depth` is 1 for the lower-dimensional forms.
|
|
static Bool ValidateTextureStorageShape(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
int dimension, GLsizei levels, GLsizei width, GLsizei height,
|
|
GLsizei depth, const char* caller) {
|
|
if (levels < 1) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "levels must be positive."));
|
|
return false;
|
|
}
|
|
// Immutable storage has to describe a real image, so unlike glTexImage*D a zero extent is
|
|
// out of range rather than a legal empty level (GL 4.6 core 8.19).
|
|
if (width < 1 || height < 1 || depth < 1) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "width, height and depth must be positive."));
|
|
return false;
|
|
}
|
|
if (!IsTextureStorageTargetForDimension(textureObject->GetTarget(), dimension)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("The effective target {} is not accepted by this entry point.",
|
|
MG_Util::ConvertTextureTargetToString(textureObject->GetTarget()))));
|
|
return false;
|
|
}
|
|
const Uint maxLevels = MaxTextureStorageLevels(textureObject->GetTarget(), width, height, depth);
|
|
if (static_cast<Uint>(levels) > maxLevels) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("levels {} exceeds the {} the level-zero size admits.", levels, maxLevels)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TextureStorage1D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
if (!ValidateTextureStorageInternalFormat(textureInternalFormat, __func__)) return;
|
|
if (!ValidateTextureStorageShape(textureObject, 1, levels, width, 1, 1, __func__)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return;
|
|
}
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
|
|
const auto textureUploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
for (GLsizei level = 0; level < levels; ++level) {
|
|
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
|
|
const SizeT byteSize = ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, 1, 1);
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, 1, 1}, byteSize});
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
|
}
|
|
// Immutable storage defines exactly `levels` levels; AllocateStorage only grows, so a
|
|
// longer pre-existing chain has to be dropped explicitly.
|
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
|
|
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
|
}
|
|
|
|
void TextureStorage2D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
if (!ValidateTextureStorageInternalFormat(textureInternalFormat, __func__)) return;
|
|
if (!ValidateTextureStorageShape(textureObject, 2, levels, width, height, 1, __func__)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return;
|
|
}
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
if (textureObject->GetTarget() == TextureTarget::TextureCubeMap && width != height) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Cube map immutable storage must be square."));
|
|
return;
|
|
}
|
|
|
|
auto textureUploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
|
|
GLenum realInternalFormat = internalformat;
|
|
GLenum realFormat = GL_RGBA;
|
|
GLenum realType = GL_UNSIGNED_BYTE;
|
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
|
MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat), PixelFormatNormalizeOptionBit::None,
|
|
&realInternalFormat, &realFormat, &realType);
|
|
const SizeT bytesPerPixel = MG_Util::GetInternalBytesPerPixel(
|
|
textureInternalFormat, MG_Util::ConvertGLEnumToTexturePixelDataType(realType));
|
|
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
// A cube map has six upload targets and glTexStorage2D allocates all of them at once (GL 4.6
|
|
// core 8.19). Allocating only the primary one left the object cube-incomplete, so every
|
|
// framebuffer it was attached to reported GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. Every other
|
|
// 2D target has exactly one upload target, so this loop is a no-op change for them.
|
|
for (const auto uploadTarget : textureObject->GetUploadTargets()) {
|
|
for (GLsizei level = 0; level < levels; ++level) {
|
|
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
|
|
const GLsizei levelHeight = std::max<GLsizei>(1, height >> level);
|
|
const SizeT byteSize =
|
|
static_cast<SizeT>(levelWidth) * static_cast<SizeT>(levelHeight) * bytesPerPixel;
|
|
textureMipmapObject->AllocateStorage(uploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize});
|
|
textureMipmapObject->MarkStorageDirty(uploadTarget, level, false);
|
|
}
|
|
// See TextureStorage1D.
|
|
textureMipmapObject->TruncateMipmapLevels(uploadTarget, static_cast<Uint>(levels));
|
|
}
|
|
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
|
}
|
|
|
|
void TextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLsizei depth) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
if (!ValidateTextureStorageInternalFormat(textureInternalFormat, __func__)) return;
|
|
if (!ValidateTextureStorageShape(textureObject, 3, levels, width, height, depth, __func__)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return;
|
|
}
|
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
|
if (textureObject->GetTarget() == TextureTarget::TextureCubeMapArray &&
|
|
(width != height || depth % 6 != 0)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", __func__,
|
|
"Cube map array immutable storage must be square with depth multiple of 6."));
|
|
return;
|
|
}
|
|
|
|
const auto textureUploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
|
|
textureObject->SetInternalFormat(textureInternalFormat);
|
|
// Array targets keep their layer count constant across levels; only true 3D
|
|
// textures halve depth per level (GL 3.3 §3.9 glTexStorage3D).
|
|
const Bool depthMips = DepthParticipatesInMipmapping(textureObject->GetTarget());
|
|
for (GLsizei level = 0; level < levels; ++level) {
|
|
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
|
|
const GLsizei levelHeight = std::max<GLsizei>(1, height >> level);
|
|
const GLsizei levelDepth = depthMips ? std::max<GLsizei>(1, depth >> level) : depth;
|
|
const SizeT byteSize = ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, levelHeight,
|
|
levelDepth);
|
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
|
|
{{levelWidth, levelHeight, levelDepth}, byteSize});
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
|
}
|
|
// See TextureStorage1D.
|
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
|
|
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
|
}
|
|
|
|
// Shared front half of glTextureStorage2DMultisample/3DMultisample. The target forms are reached
|
|
// through a binding and get their target validated there; by name the object itself has to be
|
|
// checked, and so do the extents, which the binding path never sees (GL 4.6 core 8.19).
|
|
static Bool ValidateNamedMultisampleStorage(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
TextureTarget expectedTarget, GLsizei samples, GLsizei width,
|
|
GLsizei height, GLsizei depth, const char* caller) {
|
|
if (!textureObject) return false;
|
|
if (textureObject->GetTarget() != expectedTarget) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", caller,
|
|
std::format("The effective target {} is not accepted by this entry point.",
|
|
MG_Util::ConvertTextureTargetToString(textureObject->GetTarget()))));
|
|
return false;
|
|
}
|
|
if (width < 1 || height < 1 || depth < 1) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "width, height and depth must be positive."));
|
|
return false;
|
|
}
|
|
const auto& limits = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
|
if (width > limits.MaxTextureSize || height > limits.MaxTextureSize) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"width and height must not exceed GL_MAX_TEXTURE_SIZE."));
|
|
return false;
|
|
}
|
|
if (depth > limits.MaxArrayTextureLayers) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"depth must not exceed GL_MAX_ARRAY_TEXTURE_LAYERS."));
|
|
return false;
|
|
}
|
|
// More samples than the implementation offers is a request it cannot serve rather than a
|
|
// malformed argument, so INVALID_OPERATION and not INVALID_VALUE. The limit comes from the
|
|
// getter rather than the backend parameter it is derived from, because the frontend raises
|
|
// that number - validating against the raw one would reject a count GL_MAX_SAMPLES
|
|
// advertises.
|
|
GLint maxSamples = 1;
|
|
GetIntegerv(GL_MAX_SAMPLES, &maxSamples);
|
|
if (samples > maxSamples) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "samples exceeds GL_MAX_SAMPLES."));
|
|
return false;
|
|
}
|
|
// Storage is defined once. The mipmap forms go through ValidateTextureMutable for this; the
|
|
// multisample ones reached the backend without ever asking.
|
|
if (!ValidateTextureMutable(textureObject, caller)) return false;
|
|
return true;
|
|
}
|
|
|
|
void TextureStorage2DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLboolean fixedsamplelocations) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateNamedMultisampleStorage(textureObject, TextureTarget::Texture2DMultisample, samples, width, height,
|
|
1, __func__))
|
|
return;
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
TexStorage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
|
|
});
|
|
}
|
|
|
|
void TextureStorage3DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateNamedMultisampleStorage(textureObject, TextureTarget::Texture2DMultisampleArray, samples, width,
|
|
height, depth, __func__))
|
|
return;
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
TexStorage3DMultisample(target, samples, internalformat, width, height, depth, fixedsamplelocations);
|
|
});
|
|
}
|
|
|
|
void TexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) {
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
|
|
// Not ValidateTextureUploadTarget: GL_TEXTURE_CUBE_MAP is a legal glTexStorage2D target but
|
|
// has no single upload target - it allocates all six faces - so validating one would reject
|
|
// it. The accepted set for this entry point is the dimension's storage targets, and the
|
|
// by-name form below does the per-face work.
|
|
if (!IsTextureStorageTargetForDimension(textureTarget, 1)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
std::format("Target {} does not take 1D immutable storage.",
|
|
MG_Util::ConvertGLEnumToString(target))));
|
|
return;
|
|
}
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!TextureImpl::ValidateTextureNotDefault(textureObject, __func__)) return;
|
|
|
|
TextureStorage1D(textureObject->GetExternalIndex(), levels, internalformat, width);
|
|
}
|
|
|
|
void TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) {
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
|
|
// Not ValidateTextureUploadTarget: GL_TEXTURE_CUBE_MAP is a legal glTexStorage2D target but
|
|
// has no single upload target - it allocates all six faces - so validating one would reject
|
|
// it. The accepted set for this entry point is the dimension's storage targets, and the
|
|
// by-name form below does the per-face work.
|
|
if (!IsTextureStorageTargetForDimension(textureTarget, 2)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
std::format("Target {} does not take 2D immutable storage.",
|
|
MG_Util::ConvertGLEnumToString(target))));
|
|
return;
|
|
}
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!TextureImpl::ValidateTextureNotDefault(textureObject, __func__)) return;
|
|
|
|
TextureStorage2D(textureObject->GetExternalIndex(), levels, internalformat, width, height);
|
|
}
|
|
|
|
void TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLsizei depth) {
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) return;
|
|
// Not ValidateTextureUploadTarget: GL_TEXTURE_CUBE_MAP is a legal glTexStorage2D target but
|
|
// has no single upload target - it allocates all six faces - so validating one would reject
|
|
// it. The accepted set for this entry point is the dimension's storage targets, and the
|
|
// by-name form below does the per-face work.
|
|
if (!IsTextureStorageTargetForDimension(textureTarget, 3)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
std::format("Target {} does not take 3D immutable storage.",
|
|
MG_Util::ConvertGLEnumToString(target))));
|
|
return;
|
|
}
|
|
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
|
|
auto& textureObject = bindingSlot.GetBoundObject();
|
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
|
if (!TextureImpl::ValidateTextureNotDefault(textureObject, __func__)) return;
|
|
|
|
TextureStorage3D(textureObject->GetExternalIndex(), levels, internalformat, width, height, depth);
|
|
}
|
|
|
|
// The multisample storage forms allocate exactly what the glTexImage*Multisample ones do, and
|
|
// then freeze it: TEXTURE_IMMUTABLE_FORMAT becomes TRUE and a second call is INVALID_OPERATION
|
|
// (GL 4.6 core 8.19). Only the allocation was shared before, so a multisample texture stayed
|
|
// mutable forever and could be respecified any number of times.
|
|
static void TexStorageMultisample_State(GLenum target, Bool allocated, const char* caller) {
|
|
static_cast<void>(caller);
|
|
if (!allocated) return;
|
|
const TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
if (TextureImpl::IsProxyTextureTarget(MG_Util::ConvertGLEnumToTextureUploadTarget(target))) return;
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
|
if (!textureObject) return;
|
|
textureObject->SetImmutableLevels(1);
|
|
}
|
|
|
|
void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLboolean fixedsamplelocations) {
|
|
const TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
if (!ValidateTextureMutable(activeUnit.GetBindingSlot(textureTarget).GetBoundObject(), __func__)) return;
|
|
TexStorageMultisample_State(
|
|
target, TexImage2DMultisample_State(target, samples, internalformat, width, height, fixedsamplelocations),
|
|
__func__);
|
|
}
|
|
|
|
void TexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) {
|
|
const TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
if (!ValidateTextureMutable(activeUnit.GetBindingSlot(textureTarget).GetBoundObject(), __func__)) return;
|
|
TexStorageMultisample_State(target,
|
|
TexImage3DMultisample_State(target, samples, internalformat, width, height, depth,
|
|
fixedsamplelocations),
|
|
__func__);
|
|
}
|
|
|
|
void TextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type,
|
|
const void* pixels) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
TexSubImage1D_State(target, level, xoffset, width, format, type, pixels);
|
|
});
|
|
}
|
|
|
|
void TextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
|
|
GLenum format, GLenum type, const void* pixels) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
|
|
TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
|
TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
|
if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return;
|
|
if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return;
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return;
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return;
|
|
}
|
|
|
|
auto textureUploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
|
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureObject->GetFormat(),
|
|
texturePixelDataType))
|
|
return;
|
|
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level is out of range."));
|
|
return;
|
|
}
|
|
if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height)) return;
|
|
// This entry point does not go through TexSubImage2D_State, so it needs the unpack-buffer
|
|
// rules of its own.
|
|
if (!ValidatePixelUnpackBufferSource(pixels, textureInputFormat, texturePixelDataType, {width, height, 1},
|
|
__func__))
|
|
return;
|
|
|
|
const void* originalPixels = pixels;
|
|
const auto& pixelUnpackBufferObject =
|
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
|
|
if (pixelUnpackBufferObject) {
|
|
originalPixels = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) +
|
|
reinterpret_cast<SizeT>(pixels);
|
|
}
|
|
if (!originalPixels) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"No data supplied from pixels parameter and no PBO bound."));
|
|
return;
|
|
}
|
|
|
|
SizeT inputSize = 0;
|
|
void* processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(
|
|
originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureObject->GetFormat(),
|
|
textureInputFormat, texturePixelDataType, {width, height, 1}, false, inputSize);
|
|
if (!processedPixels || inputSize == 0) {
|
|
if (processedPixels) free(processedPixels);
|
|
return;
|
|
}
|
|
|
|
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level);
|
|
const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureObject->GetFormat(), texturePixelDataType);
|
|
const SizeT srcRowSize = static_cast<SizeT>(width) * internalBpp;
|
|
const SizeT destRowSize = static_cast<SizeT>(texelSize.x()) * internalBpp;
|
|
|
|
const auto* srcData = static_cast<const Uint8*>(processedPixels);
|
|
Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level));
|
|
if (destData) {
|
|
for (GLsizei y = 0; y < height; ++y) {
|
|
const SizeT destRowOffset = static_cast<SizeT>(yoffset + y) * destRowSize +
|
|
static_cast<SizeT>(xoffset) * internalBpp;
|
|
const SizeT srcRowOffset = static_cast<SizeT>(y) * srcRowSize;
|
|
Memcpy(destData + destRowOffset, srcData + srcRowOffset, srcRowSize);
|
|
}
|
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
|
|
}
|
|
free(processedPixels);
|
|
}
|
|
|
|
void TextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
TexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
|
|
});
|
|
}
|
|
|
|
void TextureParameteri(GLuint texture, GLenum pname, GLint param) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
TextureParameterObject_State(textureObject, pname, param, __func__);
|
|
}
|
|
|
|
void TextureParameterf(GLuint texture, GLenum pname, GLfloat param) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
TextureParameterObjectf_State(textureObject, pname, param, __func__);
|
|
}
|
|
|
|
void TextureParameterfv(GLuint texture, GLenum pname, const GLfloat* params) {
|
|
if (!params) return;
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameterfv_State(target, pname, params); });
|
|
}
|
|
|
|
void TextureParameteriv(GLuint texture, GLenum pname, const GLint* params) {
|
|
if (!params) return;
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameteriv_State(target, pname, params); });
|
|
}
|
|
|
|
void TextureParameterIiv(GLuint texture, GLenum pname, const GLint* params) {
|
|
if (!params) return;
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
TexParameterIiv_State(target, pname, params);
|
|
});
|
|
}
|
|
|
|
void TextureParameterIuiv(GLuint texture, GLenum pname, const GLuint* params) {
|
|
if (!params) return;
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
TexParameterIuiv_State(target, pname, params);
|
|
});
|
|
}
|
|
|
|
void BindTextureUnit(GLuint unit, GLuint texture) {
|
|
if (unit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture unit is out of range."));
|
|
return;
|
|
}
|
|
|
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(static_cast<Int>(unit));
|
|
if (texture == 0) {
|
|
// GL 4.5 8.1: texture zero unbinds every target of the unit, i.e. rebinds each
|
|
// target's default texture object (the unit's initial state).
|
|
Bool changed = false;
|
|
for (auto& slot : textureUnit.GetAllBindingSlots()) {
|
|
if (slot.Bind(MG_State::pGLContext->GetDefaultTextureObject(slot.GetTarget()))) changed = true;
|
|
}
|
|
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit), changed);
|
|
return;
|
|
}
|
|
|
|
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
|
if (!textureObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture object does not exist."));
|
|
return;
|
|
}
|
|
const Bool changed = textureUnit.GetBindingSlot(textureObject->GetTarget()).Bind(textureObject);
|
|
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit), changed);
|
|
}
|
|
|
|
void GetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
if (!ValidateTextureImageQuery(textureObject, level, MG_Util::ConvertGLEnumToTextureInputFormat(format),
|
|
MG_Util::ConvertGLEnumToTexturePixelDataType(type), bufSize, pixels,
|
|
__func__)) {
|
|
return;
|
|
}
|
|
const auto uploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
if (MG_Backend::pActiveBackendObject != nullptr &&
|
|
MG_Backend::pActiveBackendObject->GetBackendType() == BackendType::DirectVulkan &&
|
|
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage != nullptr) {
|
|
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type,
|
|
bufSize, pixels);
|
|
return;
|
|
}
|
|
CopyTextureImageToClientOrPBO_State(textureObject, uploadTarget, level, format, type, bufSize, pixels,
|
|
__func__);
|
|
}
|
|
|
|
void GetCompressedTextureImage(GLuint texture, GLint level, GLsizei bufSize, void* pixels) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
// Level first: GL 4.6 core 8.11 wants INVALID_VALUE for an out-of-range level even when the
|
|
// texture would also fail the compressed check below.
|
|
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
|
|
|
|
// Unlike glGetTextureImage this never asks a backend: the compressed image only ever exists
|
|
// in the CPU shadow (no backend was handed the compressed bytes at all), so the shadow is
|
|
// authoritative rather than potentially stale.
|
|
CopyCompressedTextureImageToClientOrPBO(textureObject, GetPrimaryUploadTarget(textureObject), level, bufSize,
|
|
pixels, __func__);
|
|
}
|
|
|
|
void GetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
if (level < 0 || xoffset < 0 || yoffset < 0 || zoffset < 0 || width < 0 || height < 0 || depth < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture sub-image range is invalid."));
|
|
return;
|
|
}
|
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
|
|
return;
|
|
}
|
|
|
|
const auto uploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level is out of range."));
|
|
return;
|
|
}
|
|
|
|
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, static_cast<Uint>(level));
|
|
const Bool isFullLevelRead = xoffset == 0 && yoffset == 0 && zoffset == 0 &&
|
|
width == texelSize.x() && height == texelSize.y() &&
|
|
depth == texelSize.z();
|
|
if (!isFullLevelRead) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Partial texture sub-image readback is not implemented yet."));
|
|
return;
|
|
}
|
|
|
|
GetTextureImage(texture, level, format, type, bufSize, pixels);
|
|
}
|
|
|
|
// A buffer texture carries none of the sampler or level state these queries report. Reached by
|
|
// name there is no target token to blame, so the wrong object is INVALID_OPERATION rather than
|
|
// the INVALID_ENUM the target forms report for an unaccepted target (GL 4.6 core 8.11).
|
|
static Bool ValidateNamedTextureHasParameters(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const char* caller) {
|
|
if (!textureObject) return false;
|
|
if (textureObject->GetStorageType() == TextureStorageType::Buffer) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"The effective target of `texture` has no texture parameters."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameteriv_State(target, pname, params); });
|
|
}
|
|
|
|
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterfv_State(target, pname, params); });
|
|
}
|
|
|
|
void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); });
|
|
}
|
|
|
|
void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); });
|
|
}
|
|
|
|
void GetTextureLevelParameteriv(GLuint texture, GLint level, GLenum pname, GLint* params) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
GetTexLevelParameteriv_State(target, level, pname, params);
|
|
});
|
|
}
|
|
|
|
void GetTextureLevelParameterfv(GLuint texture, GLint level, GLenum pname, GLfloat* params) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
GetTexLevelParameterfv_State(target, level, pname, params);
|
|
});
|
|
}
|
|
|
|
void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access,
|
|
GLenum format) {
|
|
if (unit >= GetAdvertisedImageUnitCount()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Image texture unit is out of range."));
|
|
return;
|
|
}
|
|
if (level < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level must be non-negative."));
|
|
return;
|
|
}
|
|
if (layer < 0 && layered == GL_FALSE) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture layer must be non-negative."));
|
|
return;
|
|
}
|
|
if (access != GL_READ_ONLY && access != GL_WRITE_ONLY && access != GL_READ_WRITE) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Invalid image texture access."));
|
|
return;
|
|
}
|
|
if (!IsValidImageTextureFormat(format)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Invalid image texture format."));
|
|
return;
|
|
}
|
|
|
|
SharedPtr<MG_State::GLState::ITextureObject> textureObject;
|
|
if (texture != 0) {
|
|
if (!MG_State::pGLContext->ValidateTextureObject(texture)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture name is not a texture object."));
|
|
return;
|
|
}
|
|
textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
|
}
|
|
|
|
auto bindImageTexture = MG_Backend::gBackendFunctionsTable.GL.BindImageTexture;
|
|
if (!bindImageTexture) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"Backend does not support image texture binding."));
|
|
return;
|
|
}
|
|
|
|
MG_State::pGLContext->GetImageTextureBinding(static_cast<Int>(unit))
|
|
.Bind(textureObject, level, layered, layer, access, format);
|
|
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit));
|
|
bindImageTexture(unit, texture, level, layered, layer, access, format);
|
|
}
|
|
|
|
// GL 4.6 core 8.14.4: a cube map that is not cube complete has no consistent set of faces to
|
|
// filter down, so generating its mipmaps is INVALID_OPERATION. Without this the incomplete
|
|
// texture reached the backend, where DirectVulkan asserts on it and takes the process down.
|
|
Bool ValidateGenerateMipmapTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
|
const char* caller) {
|
|
if (!textureObject) return false;
|
|
const auto target = textureObject->GetTarget();
|
|
if ((target == TextureTarget::TextureCubeMap || target == TextureTarget::TextureCubeMapArray) &&
|
|
!textureObject->IsComplete()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
"Mipmap generation requires a cube complete cube map texture."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GenerateMipmap(GLenum target) {
|
|
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
if (!TextureImpl::ValidateTextureTarget(textureTarget)) {
|
|
return;
|
|
}
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
auto& textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
|
if (!textureObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "GenerateMipmap requires a bound texture."));
|
|
return;
|
|
}
|
|
if (!ValidateGenerateMipmapTexture(textureObject, __func__)) return;
|
|
|
|
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires mipmap texture storage.");
|
|
EnsureGeneratedMipmapStorageAllocated(*mipmapTexture);
|
|
GenerateMipmap_Backend(target);
|
|
}
|
|
|
|
void GenerateTextureMipmap(GLuint texture) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!ValidateGenerateMipmapTexture(textureObject, __func__)) return;
|
|
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
|
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateTextureMipmap requires mipmap texture storage.");
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
|
|
EnsureGeneratedMipmapStorageAllocated(*mipmapTexture);
|
|
GenerateMipmap_Backend(target);
|
|
});
|
|
}
|
|
|
|
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
|
|
if (!GetTexImage_State(target, level, format, type, pixels)) return;
|
|
if (MG_Backend::gBackendFunctionsTable.GL.GetTexImage != nullptr) {
|
|
GetTexImage_Backend(target, level, format, type, pixels);
|
|
return;
|
|
}
|
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
const auto& textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
|
CopyTextureImageToClientOrPBO_State(textureObject, textureUploadTarget, level, format, type, -1, pixels,
|
|
__func__);
|
|
}
|
|
|
|
void GetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params) {
|
|
if (!params || bufSize <= 0) return;
|
|
|
|
const TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
|
const Bool isRenderbufferTarget = target == GL_RENDERBUFFER;
|
|
if (!isRenderbufferTarget && !TextureImpl::ValidateTextureTarget(textureTarget)) return;
|
|
|
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
|
textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, TextureInputFormat::RGBA,
|
|
TexturePixelDataType::UnsignedByte);
|
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
|
|
|
GLenum preferredInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat);
|
|
GLenum imageFormat = GL_RGBA;
|
|
GLenum imageType = GL_UNSIGNED_BYTE;
|
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(preferredInternalFormat, PixelFormatNormalizeOptionBit::None,
|
|
&preferredInternalFormat, &imageFormat, &imageType);
|
|
|
|
auto writeValues = [&](std::initializer_list<GLint> values) {
|
|
GLsizei index = 0;
|
|
for (GLint value : values) {
|
|
if (index >= bufSize) break;
|
|
params[index++] = value;
|
|
}
|
|
while (index < bufSize) {
|
|
params[index++] = 0;
|
|
}
|
|
};
|
|
|
|
const Bool isDepthFormat = MG_Util::IsDepthFormatInternalFormat(textureInternalFormat);
|
|
const Bool isStencilFormat = MG_Util::IsStencilFormatInternalFormat(textureInternalFormat);
|
|
const ComponentSizes componentSizes = MG_Util::GetComponentSizesForInternalFormat(textureInternalFormat);
|
|
const Bool isIntegerFormat = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER ||
|
|
imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER;
|
|
|
|
SizeT targetIndex = isRenderbufferTarget ? MG_Backend::GetRenderbufferFormatCapabilityTargetIndex()
|
|
: MG_Backend::GetFormatCapabilityTargetIndex(textureTarget);
|
|
if (targetIndex >= MG_Backend::kFormatCapabilityTargetCount) return;
|
|
const SizeT formatIndex = static_cast<SizeT>(textureInternalFormat);
|
|
|
|
MG_Backend::FormatCapabilityFlags fullCaps{};
|
|
MG_Backend::FormatCapabilityFlags caveatCaps{};
|
|
const Vector<Int>* sampleCounts = nullptr;
|
|
if (MG_Backend::pActiveBackendObject) {
|
|
const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities();
|
|
fullCaps = cache.FullCaps[targetIndex][formatIndex];
|
|
caveatCaps = cache.CaveatCaps[targetIndex][formatIndex];
|
|
sampleCounts = &cache.SampleCounts[targetIndex][formatIndex];
|
|
}
|
|
|
|
auto hasFull = [&](MG_Backend::FormatCapability capability) {
|
|
return MG_Backend::HasFormatCapability(fullCaps, capability);
|
|
};
|
|
auto hasCaveat = [&](MG_Backend::FormatCapability capability) {
|
|
return MG_Backend::HasFormatCapability(caveatCaps, capability);
|
|
};
|
|
auto supportFor = [&](MG_Backend::FormatCapability capability) -> GLint {
|
|
if (hasFull(capability)) return GL_FULL_SUPPORT;
|
|
if (hasCaveat(capability)) return GL_CAVEAT_SUPPORT;
|
|
return GL_NONE;
|
|
};
|
|
auto supportForWithFallback = [&](MG_Backend::FormatCapability primary,
|
|
MG_Backend::FormatCapability fallback) -> GLint {
|
|
if (hasFull(primary)) return GL_FULL_SUPPORT;
|
|
if (hasCaveat(primary) || hasFull(fallback) || hasCaveat(fallback)) return GL_CAVEAT_SUPPORT;
|
|
return GL_NONE;
|
|
};
|
|
switch (pname) {
|
|
case GL_INTERNALFORMAT_SUPPORTED:
|
|
writeValues({(hasFull(MG_Backend::FormatCapability::Creatable) ||
|
|
hasCaveat(MG_Backend::FormatCapability::Creatable))
|
|
? GL_TRUE
|
|
: GL_FALSE});
|
|
return;
|
|
case GL_INTERNALFORMAT_PREFERRED:
|
|
writeValues({static_cast<GLint>(preferredInternalFormat)});
|
|
return;
|
|
case GL_INTERNALFORMAT_RED_SIZE:
|
|
writeValues({componentSizes.Red});
|
|
return;
|
|
case GL_INTERNALFORMAT_GREEN_SIZE:
|
|
writeValues({componentSizes.Green});
|
|
return;
|
|
case GL_INTERNALFORMAT_BLUE_SIZE:
|
|
writeValues({componentSizes.Blue});
|
|
return;
|
|
case GL_INTERNALFORMAT_ALPHA_SIZE:
|
|
writeValues({componentSizes.Alpha});
|
|
return;
|
|
case GL_INTERNALFORMAT_DEPTH_SIZE:
|
|
writeValues({componentSizes.Depth});
|
|
return;
|
|
case GL_INTERNALFORMAT_STENCIL_SIZE:
|
|
writeValues({componentSizes.Stencil});
|
|
return;
|
|
case GL_INTERNALFORMAT_SHARED_SIZE:
|
|
writeValues({textureInternalFormat == TextureInternalFormat::RGB9E5 ? 5 : 0});
|
|
return;
|
|
case GL_INTERNALFORMAT_RED_TYPE:
|
|
writeValues({GetTextureComponentType(textureInternalFormat, componentSizes.Red, false, false)});
|
|
return;
|
|
case GL_INTERNALFORMAT_GREEN_TYPE:
|
|
writeValues({GetTextureComponentType(textureInternalFormat, componentSizes.Green, false, false)});
|
|
return;
|
|
case GL_INTERNALFORMAT_BLUE_TYPE:
|
|
writeValues({GetTextureComponentType(textureInternalFormat, componentSizes.Blue, false, false)});
|
|
return;
|
|
case GL_INTERNALFORMAT_ALPHA_TYPE:
|
|
writeValues({GetTextureComponentType(textureInternalFormat, componentSizes.Alpha, false, false)});
|
|
return;
|
|
case GL_INTERNALFORMAT_DEPTH_TYPE:
|
|
writeValues({GetTextureComponentType(textureInternalFormat, componentSizes.Depth, true, false)});
|
|
return;
|
|
case GL_INTERNALFORMAT_STENCIL_TYPE:
|
|
writeValues({GetTextureComponentType(textureInternalFormat, componentSizes.Stencil, false, true)});
|
|
return;
|
|
case GL_TEXTURE_IMAGE_FORMAT:
|
|
writeValues({static_cast<GLint>(imageFormat)});
|
|
return;
|
|
case GL_TEXTURE_IMAGE_TYPE:
|
|
writeValues({static_cast<GLint>(imageType)});
|
|
return;
|
|
case GL_TEXTURE_COMPRESSED:
|
|
case GL_TEXTURE_COMPRESSED_BLOCK_WIDTH:
|
|
case GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT:
|
|
case GL_TEXTURE_COMPRESSED_BLOCK_SIZE:
|
|
writeValues({0});
|
|
return;
|
|
case GL_COLOR_COMPONENTS:
|
|
writeValues({(!isDepthFormat && !isStencilFormat) ? GL_TRUE : GL_FALSE});
|
|
return;
|
|
case GL_DEPTH_COMPONENTS:
|
|
writeValues({isDepthFormat ? GL_TRUE : GL_FALSE});
|
|
return;
|
|
case GL_STENCIL_COMPONENTS:
|
|
writeValues({isStencilFormat ? GL_TRUE : GL_FALSE});
|
|
return;
|
|
case GL_FRAMEBUFFER_RENDERABLE:
|
|
writeValues({supportFor(MG_Backend::FormatCapability::FramebufferRenderable)});
|
|
return;
|
|
case GL_FRAMEBUFFER_RENDERABLE_LAYERED:
|
|
writeValues({supportFor(MG_Backend::FormatCapability::FramebufferLayered)});
|
|
return;
|
|
case GL_FILTER:
|
|
writeValues({supportForWithFallback(MG_Backend::FormatCapability::LinearFilter,
|
|
MG_Backend::FormatCapability::Sampled)});
|
|
return;
|
|
case GL_MIPMAP:
|
|
writeValues({supportForWithFallback(MG_Backend::FormatCapability::GenerateMipmap,
|
|
MG_Backend::FormatCapability::Sampled)});
|
|
return;
|
|
case GL_TEXTURE_GATHER:
|
|
case GL_TEXTURE_GATHER_SHADOW:
|
|
writeValues({supportFor(MG_Backend::FormatCapability::TextureGather)});
|
|
return;
|
|
case GL_TEXTURE_SHADOW:
|
|
writeValues({supportFor(MG_Backend::FormatCapability::TextureShadow)});
|
|
return;
|
|
case GL_NUM_SAMPLE_COUNTS:
|
|
writeValues({sampleCounts != nullptr ? static_cast<GLint>(sampleCounts->size()) : 0});
|
|
return;
|
|
case GL_SAMPLES:
|
|
if (sampleCounts != nullptr) {
|
|
GLsizei index = 0;
|
|
for (Int sampleCount : *sampleCounts) {
|
|
if (index >= bufSize) break;
|
|
params[index++] = sampleCount;
|
|
}
|
|
while (index < bufSize) {
|
|
params[index++] = 0;
|
|
}
|
|
return;
|
|
}
|
|
writeValues({});
|
|
return;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"pname is not supported by GetInternalformativ."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void GetMultisamplefv(GLenum pname, GLuint index, GLfloat* val) {
|
|
if (val == nullptr) return;
|
|
if (pname != GL_SAMPLE_POSITION) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Only GL_SAMPLE_POSITION is supported."));
|
|
return;
|
|
}
|
|
|
|
const Int maxSamples = MG_Backend::pActiveBackendObject != nullptr
|
|
? std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxSamples, 1)
|
|
: 1;
|
|
if (static_cast<Int>(index) >= maxSamples) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Sample index is out of range."));
|
|
return;
|
|
}
|
|
|
|
// Keep sample positions deterministic even before the backend exposes vendor-specific patterns.
|
|
val[0] = 0.5f;
|
|
val[1] = 0.5f;
|
|
}
|
|
|
|
void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) {
|
|
TexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
|
|
}
|
|
|
|
void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
|
|
GLenum format, GLenum type, const void* pixels) {
|
|
TexSubImage2D_State(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
|
}
|
|
|
|
void TexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type,
|
|
const GLvoid* pixels) {
|
|
TexSubImage1D_State(target, level, xoffset, width, format, type, pixels);
|
|
}
|
|
|
|
void TexParameterf(GLenum target, GLenum pname, GLfloat param) {
|
|
TexParameterf_State(target, pname, param);
|
|
}
|
|
|
|
void TexParameteri(GLenum target, GLenum pname, GLint param) {
|
|
TexParameteri_State(target, pname, param);
|
|
}
|
|
|
|
void TexParameterfv(GLenum target, GLenum pname, const GLfloat* params) {
|
|
TexParameterfv_State(target, pname, params);
|
|
}
|
|
|
|
void TexParameteriv(GLenum target, GLenum pname, const GLint* params) {
|
|
TexParameteriv_State(target, pname, params);
|
|
}
|
|
|
|
void TexParameterIiv(GLenum target, GLenum pname, const GLint* params) {
|
|
TexParameterIiv_State(target, pname, params);
|
|
}
|
|
|
|
void TexParameterIuiv(GLenum target, GLenum pname, const GLuint* params) {
|
|
TexParameterIuiv_State(target, pname, params);
|
|
}
|
|
|
|
void TexImage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLsizei depth, GLboolean fixedsamplelocations) {
|
|
TexImage3DMultisample_State(target, samples, internalformat, width, height, depth, fixedsamplelocations);
|
|
}
|
|
|
|
void TexImage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLboolean fixedsamplelocations) {
|
|
TexImage2DMultisample_State(target, samples, internalformat, width, height, fixedsamplelocations);
|
|
}
|
|
|
|
void TexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth,
|
|
GLint border, GLenum format, GLenum type, const void* pixels) {
|
|
TexImage3D_State(target, level, internalformat, width, height, depth, border, format, type, pixels);
|
|
}
|
|
|
|
void TexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border,
|
|
GLenum format, GLenum type, const void* pixels) {
|
|
TexImage2D_State(target, level, internalformat, width, height, border, format, type, pixels);
|
|
}
|
|
|
|
void TexImage1D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format,
|
|
GLenum type, const GLvoid* pixels) {
|
|
TexImage1D_State(target, level, internalFormat, width, border, format, type, pixels);
|
|
}
|
|
|
|
void TexBuffer(GLenum target, GLenum internalformat, GLuint buffer) {
|
|
TexBuffer_State(target, internalformat, buffer);
|
|
}
|
|
|
|
// The buffer texture bound to `target` on the active unit - what the non-DSA range form
|
|
// operates on. Kept separate from TexBuffer_State because that one resolves the target
|
|
// itself and attaches the whole buffer.
|
|
static const SharedPtr<MG_State::GLState::ITextureObject>& GetBoundBufferTexture(GLenum target,
|
|
const char* caller) {
|
|
TextureUploadTarget uploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
|
if (!TextureImpl::ValidateTextureUploadTarget(uploadTarget)) return nullTextureObject;
|
|
(void)caller;
|
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
|
return activeUnit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)).GetBoundObject();
|
|
}
|
|
|
|
void TexBufferRange(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
|
AttachBufferToTexture(GetBoundBufferTexture(target, __func__), internalformat, buffer, offset,
|
|
static_cast<SizeT>(size < 0 ? 0 : size), __func__);
|
|
}
|
|
|
|
void TextureBuffer(GLuint texture, GLenum internalformat, GLuint buffer) {
|
|
AttachBufferToTexture(GetTextureObjectByName(texture, __func__), internalformat, buffer, 0,
|
|
MG_State::GLState::TextureObjectBuffer::kWholeBuffer, __func__);
|
|
}
|
|
|
|
void TextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
|
AttachBufferToTexture(GetTextureObjectByName(texture, __func__), internalformat, buffer, offset,
|
|
static_cast<SizeT>(size < 0 ? 0 : size), __func__);
|
|
}
|
|
|
|
GLboolean IsTexture(GLuint texture) {
|
|
return IsTexture_State(texture);
|
|
}
|
|
|
|
void GetTexParameterIuiv(GLenum target, GLenum pname, GLuint* params) {
|
|
GetTexParameterIuiv_State(target, pname, params);
|
|
}
|
|
|
|
void GetTexParameterIiv(GLenum target, GLenum pname, GLint* params) {
|
|
GetTexParameterIiv_State(target, pname, params);
|
|
}
|
|
|
|
void GetTexParameteriv(GLenum target, GLenum pname, GLint* params) {
|
|
GetTexParameteriv_State(target, pname, params);
|
|
}
|
|
|
|
void GetTexParameterfv(GLenum target, GLenum pname, GLfloat* params) {
|
|
GetTexParameterfv_State(target, pname, params);
|
|
}
|
|
|
|
void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params) {
|
|
GetTexLevelParameteriv_State(target, level, pname, params);
|
|
}
|
|
|
|
void GetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params) {
|
|
GetTexLevelParameterfv_State(target, level, pname, params);
|
|
}
|
|
|
|
void GetCompressedTexImage(GLenum target, GLint level, void* img) {
|
|
GetCompressedTexImage_State(target, level, img);
|
|
}
|
|
|
|
void GenTextures(GLsizei n, GLuint* textures) {
|
|
GenTextures_State(n, textures);
|
|
}
|
|
|
|
void DeleteTextures(GLsizei n, const GLuint* textures) {
|
|
DeleteTextures_State(n, textures);
|
|
}
|
|
|
|
void CopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y,
|
|
GLsizei width, GLsizei height) {
|
|
CopyTexSubImage3D_State(target, level, xoffset, yoffset, zoffset, x, y, width, height);
|
|
}
|
|
|
|
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
|
GLsizei height) {
|
|
CopyTexSubImage2D_Backend(target, level, xoffset, yoffset, x, y, width, height);
|
|
}
|
|
|
|
void CopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
|
GLsizei width, GLsizei height) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
// GL 4.6 sec. 8.8: the 2D form only accepts these effective targets; cube maps must
|
|
// go through CopyTextureSubImage3D with the face as a layer.
|
|
const auto target = textureObject->GetTarget();
|
|
if (target != TextureTarget::Texture2D && target != TextureTarget::Texture1DArray &&
|
|
target != TextureTarget::TextureRectangle) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"CopyTextureSubImage2D requires a 2D, 1D-array, or "
|
|
"rectangle texture."));
|
|
return;
|
|
}
|
|
if (!ValidateCopyTextureSubImage(textureObject, level, xoffset, yoffset, 0, width, height, 1, __func__)) {
|
|
return;
|
|
}
|
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum glTarget) {
|
|
CopyTexSubImage2D_Backend(glTarget, level, xoffset, yoffset, x, y, width, height);
|
|
});
|
|
}
|
|
|
|
void CopyTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
if (textureObject->GetTarget() != TextureTarget::Texture1D) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"CopyTextureSubImage1D requires a 1D texture."));
|
|
return;
|
|
}
|
|
if (!ValidateCopyTextureSubImage(textureObject, level, xoffset, 0, 0, width, 1, 1, __func__)) return;
|
|
CopyReadFramebufferIntoMipmapRegion(textureObject, GetPrimaryUploadTarget(textureObject), level, xoffset,
|
|
/*yoffset=*/0, /*zoffset=*/0, x, y, width, /*height=*/1, __func__);
|
|
}
|
|
|
|
void CopyTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x,
|
|
GLint y, GLsizei width, GLsizei height) {
|
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
|
if (!textureObject) return;
|
|
// GL 4.6 core 8.6: the 3D form takes the layered targets, a cube map included - the face
|
|
// is selected by zoffset.
|
|
const auto target = textureObject->GetTarget();
|
|
if (target != TextureTarget::Texture3D && target != TextureTarget::Texture2DArray &&
|
|
target != TextureTarget::TextureCubeMap && target != TextureTarget::TextureCubeMapArray) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"CopyTextureSubImage3D requires a 3D, 2D-array, cube map, or "
|
|
"cube map array texture."));
|
|
return;
|
|
}
|
|
if (!ValidateCopyTextureSubImage(textureObject, level, xoffset, yoffset, zoffset, width, height, 1, __func__)) {
|
|
return;
|
|
}
|
|
// A cube map addresses its faces as separate upload targets, so zoffset selects the target
|
|
// rather than a slice within one; every other layered target keeps zoffset as the slice.
|
|
TextureUploadTarget uploadTarget = GetPrimaryUploadTarget(textureObject);
|
|
GLint sliceOffset = zoffset;
|
|
if (target == TextureTarget::TextureCubeMap) {
|
|
uploadTarget = static_cast<TextureUploadTarget>(
|
|
static_cast<SizeT>(TextureUploadTarget::CubeMapPositiveX) + static_cast<SizeT>(zoffset));
|
|
sliceOffset = 0;
|
|
}
|
|
CopyReadFramebufferIntoMipmapRegion(textureObject, uploadTarget, level, xoffset, yoffset, sliceOffset, x, y,
|
|
width, height, __func__);
|
|
}
|
|
|
|
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) {
|
|
CopyTexSubImage1D_State(target, level, xoffset, x, y, width);
|
|
}
|
|
|
|
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
|
GLsizei height, GLint border) {
|
|
if (!CopyTexImage2D_State(target, level, internalformat, x, y, width, height, border)) return;
|
|
CopyTexImage2D_Backend(target, level, internalformat, x, y, width, height, border);
|
|
}
|
|
|
|
void CopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
|
GLint border) {
|
|
CopyTexImage1D_State(target, level, internalformat, x, y, width, border);
|
|
}
|
|
|
|
void CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
|
GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
|
auto srcTexture = GetTextureObjectByName(srcName, __func__);
|
|
auto dstTexture = GetTextureObjectByName(dstName, __func__);
|
|
if (!ValidateCopyImageSubData_State(srcTexture, srcTarget, srcLevel, dstTexture, dstTarget, dstLevel,
|
|
srcWidth, srcHeight, srcDepth)) {
|
|
return;
|
|
}
|
|
CopyImageSubData_Backend(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, dstTexture, dstTarget, dstLevel,
|
|
dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);
|
|
}
|
|
|
|
void CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) {
|
|
CompressedTexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize,
|
|
data);
|
|
}
|
|
|
|
void CompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
|
GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
|
|
CompressedTexSubImage2D_State(target, level, xoffset, yoffset, width, height, format, imageSize, data);
|
|
}
|
|
|
|
void CompressedTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format,
|
|
GLsizei imageSize, const void* data) {
|
|
CompressedTexSubImage1D_State(target, level, xoffset, width, format, imageSize, data);
|
|
}
|
|
|
|
void CompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLsizei depth, GLint border, GLsizei imageSize, const void* data) {
|
|
CompressedTexImage3D_State(target, level, internalformat, width, height, depth, border, imageSize, data);
|
|
}
|
|
|
|
void CompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
|
GLint border, GLsizei imageSize, const void* data) {
|
|
CompressedTexImage2D_State(target, level, internalformat, width, height, border, imageSize, data);
|
|
}
|
|
|
|
void CompressedTexImage1D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border,
|
|
GLsizei imageSize, const void* data) {
|
|
CompressedTexImage1D_State(target, level, internalformat, width, border, imageSize, data);
|
|
}
|
|
|
|
void BindTexture(GLenum target, GLuint texture) {
|
|
BindTexture_State(target, texture);
|
|
}
|
|
|
|
void ActiveTexture(GLenum texture) {
|
|
ActiveTexture_State(texture);
|
|
}
|
|
|
|
} // namespace MobileGL::MG_Impl::GLImpl
|