external textures are no longer restricted to SAMPLER_EXTERNAL

OpenGLES allows this when GL_OES_EGL_image is present.
This commit is contained in:
Mathias Agopian
2025-01-07 09:50:57 -08:00
committed by Mathias Agopian
parent 7d271cea1d
commit ef6ef784c9
16 changed files with 135 additions and 51 deletions
@@ -138,6 +138,13 @@ Java_com_google_android_filament_Texture_nBuilderImportTexture(JNIEnv*, jclass,
builder->import((intptr_t)id);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderExternal(JNIEnv*, jclass, jlong nativeBuilder) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->external();
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Texture_nBuilderBuild(JNIEnv*, jclass,
jlong nativeBuilder, jlong nativeEngine) {
@@ -800,6 +800,19 @@ public class Texture {
return this;
}
/**
* Creates an external texture. The content must be set using setExternalImage().
* The sampler can be SAMPLER_EXTERNAL or SAMPLER_2D depending on the format. Generally
* YUV formats must use SAMPLER_EXTERNAL. This depends on the backend features and is not
* validated.
* @return This Builder, for chaining calls.
*/
@NonNull
public Builder external() {
nBuilderExternal(mNativeBuilder);
return this;
}
/**
* Creates a new <code>Texture</code> instance.
* @param engine The {@link Engine} to associate this <code>Texture</code> with.
@@ -1261,6 +1274,7 @@ public class Texture {
private static native void nBuilderUsage(long nativeBuilder, int flags);
private static native void nBuilderSwizzle(long nativeBuilder, int r, int g, int b, int a);
private static native void nBuilderImportTexture(long nativeBuilder, long id);
private static native void nBuilderExternal(long nativeBuilder);
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
private static native int nGetWidth(long nativeTexture, int level);
@@ -214,6 +214,7 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureViewSwizzle,
backend::TextureSwizzle, a)
DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImage,
backend::SamplerType, target,
backend::TextureFormat, format,
uint32_t, width,
uint32_t, height,
+3 -1
View File
@@ -482,7 +482,9 @@ void MetalDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTextu
mContext->textures.insert(construct_handle<MetalTexture>(th, *mContext, src, r, g, b, a));
}
void MetalDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::TextureFormat format,
void MetalDriver::createTextureExternalImageR(Handle<HwTexture> th,
backend::SamplerType target,
backend::TextureFormat format,
uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) {
mContext->textures.insert(construct_handle<MetalTexture>(
th, *mContext, format, width, height, usage, (CVPixelBufferRef)image));
@@ -173,6 +173,7 @@ void GLDescriptorSet::update(OpenGLContext& gl,
arg.target = t ? t->gl.target : 0;
arg.id = t ? t->gl.id : 0;
arg.external = t ? t->gl.external : false;
if constexpr (std::is_same_v<T, Sampler> ||
std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
@@ -285,7 +286,7 @@ void GLDescriptorSet::bind(
} else if constexpr (std::is_same_v<T, Sampler>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id);
gl.bindTexture(unit, arg.target, arg.id, arg.external);
gl.bindSampler(unit, arg.sampler);
if (UTILS_UNLIKELY(arg.ref)) {
updateTextureView(gl, handleAllocator, unit, arg);
@@ -296,7 +297,7 @@ void GLDescriptorSet::bind(
} else if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id);
gl.bindTexture(unit, arg.target, arg.id, arg.external);
gl.bindSampler(unit, arg.sampler);
if (UTILS_UNLIKELY(arg.ref)) {
updateTextureView(gl, handleAllocator, unit, arg);
@@ -314,7 +315,7 @@ void GLDescriptorSet::bind(
// in ES2 the sampler parameters need to be set on the texture itself
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id);
gl.bindTexture(unit, arg.target, arg.id, arg.external);
SamplerParams const params = arg.params;
glTexParameteri(arg.target, GL_TEXTURE_MIN_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMin));
@@ -111,7 +111,9 @@ private:
// A sampler descriptor
struct Sampler {
GLenum target = 0; // 4
uint16_t target; // 2 (GLenum)
bool external = false; // 1
bool reserved = false; // 1
GLuint id = 0; // 4
GLuint sampler = 0; // 4
Handle<GLTextureRef> ref; // 4
@@ -126,7 +128,9 @@ private:
};
struct SamplerWithAnisotropyWorkaround {
GLenum target = 0; // 4
uint16_t target; // 2 (GLenum)
bool external = false; // 1
bool reserved = false; // 1
GLuint id = 0; // 4
GLuint sampler = 0; // 4
Handle<GLTextureRef> ref; // 4
@@ -143,7 +147,9 @@ private:
// A sampler descriptor for ES2
struct SamplerGLES2 {
GLenum target = 0; // 4
uint16_t target; // 2 (GLenum)
bool external = false; // 1
bool reserved = false; // 1
GLuint id = 0; // 4
SamplerParams params{}; // 4
float anisotropy = 1.0f; // 4
+4 -3
View File
@@ -35,7 +35,7 @@ struct GLTextureRef {
GLTextureRef() = default;
// view reference counter
uint16_t count = 1;
// current per-view values of the texture (in GL we can only have a single View active at
// Current per-view values of the texture (in GL we can only have a single View active at
// a time, and this tracks that state). It's used to avoid unnecessarily change state.
int8_t baseLevel = 127;
int8_t maxLevel = -1;
@@ -50,7 +50,7 @@ struct GLTextureRef {
struct GLTexture : public HwTexture {
using HwTexture::HwTexture;
struct GL {
GL() noexcept : imported(false), sidecarSamples(1), reserved1(0) {}
GL() noexcept : imported(false), external(false), sidecarSamples(1), reserved1(0) {}
GLuint id = 0; // texture or renderbuffer id
GLenum target = 0;
GLenum internalFormat = 0;
@@ -62,7 +62,8 @@ struct GLTexture : public HwTexture {
int8_t maxLevel = -1;
uint8_t reserved0 = 0;
bool imported : 1;
uint8_t sidecarSamples : 4;
bool external : 1;
uint8_t sidecarSamples : 3;
uint8_t reserved1 : 3;
std::array<TextureSwizzle, 4> swizzle{
TextureSwizzle::CHANNEL_0,
+5 -3
View File
@@ -153,7 +153,7 @@ public:
void pixelStore(GLenum, GLint) noexcept;
inline void activeTexture(GLuint unit) noexcept;
inline void bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept;
inline void bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept;
void unbindTexture(GLenum target, GLuint id) noexcept;
void unbindTextureUnit(GLuint unit) noexcept;
@@ -761,7 +761,9 @@ void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
#endif
}
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexcept {
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept {
// another texture is bound to the same unit with a different target,
// unbind the texture from the current target
update_state(state.textures.units[unit].target, target, [&]() {
activeTexture(unit);
glBindTexture(state.textures.units[unit].target, 0);
@@ -769,7 +771,7 @@ void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId) noexce
update_state(state.textures.units[unit].id, texId, [&]() {
activeTexture(unit);
glBindTexture(target, texId);
}, target == GL_TEXTURE_EXTERNAL_OES);
}, external);
}
void OpenGLContext::useProgram(GLuint program) noexcept {
+30 -7
View File
@@ -402,7 +402,7 @@ void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
assert_invariant(t != nullptr);
mContext.bindTexture(unit, t->gl.target, t->gl.id);
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
}
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
@@ -1052,14 +1052,36 @@ void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::TextureFormat format,
uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) {
void OpenGLDriver::createTextureExternalImageR(Handle<HwTexture> th, SamplerType target,
TextureFormat format, uint32_t width, uint32_t height, TextureUsage usage, void* image) {
DEBUG_MARKER()
createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage);
GLTexture* t = handle_cast<GLTexture*>(th);
usage |= TextureUsage::SAMPLEABLE;
usage &= ~TextureUsage::UPLOADABLE;
auto& gl = mContext;
GLenum internalFormat = getInternalFormat(format);
if (UTILS_UNLIKELY(gl.isES2())) {
// on ES2, format and internal format must match
// FIXME: handle compressed texture format
internalFormat = textureFormatToFormatAndType(format).first;
}
assert_invariant(internalFormat);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage);
assert_invariant(t);
assert_invariant(t->target == SamplerType::SAMPLER_EXTERNAL);
t->externalTexture = mPlatform.createExternalImageTexture();
if (t->externalTexture) {
t->gl.target = t->externalTexture->target;
t->gl.id = t->externalTexture->id;
// internalFormat actually depends on the external image, but it doesn't matter
// because it's not used anywhere for anything important.
t->gl.internalFormat = internalFormat;
t->gl.baseLevel = 0;
t->gl.maxLevel = 0;
t->gl.external = true; // forces bindTexture() call (they're never cached)
}
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
if (mPlatform.setExternalImage(image, t->externalTexture)) {
@@ -1092,6 +1114,7 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
switch (target) {
case SamplerType::SAMPLER_EXTERNAL:
t->gl.target = GL_TEXTURE_EXTERNAL_OES;
t->gl.external = true; // forces bindTexture() call (they're never cached)
break;
case SamplerType::SAMPLER_2D:
t->gl.target = GL_TEXTURE_2D;
@@ -3503,7 +3526,7 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) {
auto& gl = mContext;
gl.bindVertexArray(nullptr);
for (int unit = OpenGLContext::DUMMY_TEXTURE_BINDING; unit >= 0; unit--) {
gl.bindTexture(unit, GL_TEXTURE_2D, 0);
gl.bindTexture(unit, GL_TEXTURE_2D, 0, false);
}
gl.disable(GL_CULL_FACE);
gl.depthFunc(GL_LESS);
@@ -715,14 +715,16 @@ void PlatformEGL::destroyExternalImage(ExternalTexture* texture) noexcept {
bool PlatformEGL::setExternalImage(void* externalImage,
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
if (UTILS_LIKELY(ext.gl.OES_EGL_image_external_essl3)) {
assert_invariant(texture->target == GL_TEXTURE_EXTERNAL_OES);
// OES_EGL_image_external_essl3 must be present if the target is TEXTURE_EXTERNAL_OES
// GL_OES_EGL_image must be present if TEXTURE_2D is used
#if defined(GL_OES_EGL_image) || defined(GL_OES_EGL_image_external_essl3)
// the texture is guaranteed to be bound here.
#ifdef GL_OES_EGL_image
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES,
glEGLImageTargetTexture2DOES(texture->target,
static_cast<GLeglImageOES>(externalImage));
#endif
}
return true;
}
+2 -1
View File
@@ -543,7 +543,8 @@ void VulkanDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
texture.inc();
}
void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::TextureFormat format,
void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th,
backend::SamplerType target, backend::TextureFormat format,
uint32_t width, uint32_t height, backend::TextureUsage usage, void* externalImage) {
FVK_SYSTRACE_SCOPE();
@@ -227,7 +227,8 @@ TEST_F(BackendTest, RenderExternalImage) {
api.setupExternalImage(pixBuffer);
backend::Handle<HwTexture> texture =
api.createTextureExternalImage(TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer);
api.createTextureExternalImage(SamplerType::SAMPLER_EXTERNAL,
TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer);
// We're now free to release the buffer.
CVBufferRelease(pixBuffer);
+14 -2
View File
@@ -261,6 +261,18 @@ public:
*/
Builder& import(intptr_t id) noexcept;
/**
* Creates an external texture. The content must be set using setExternalImage().
* The sampler can be SAMPLER_EXTERNAL or SAMPLER_2D depending on the format. Generally
* YUV formats must use SAMPLER_EXTERNAL. This depends on the backend features and is not
* validated.
*
* If the Sampler is set to SAMPLER_EXTERNAL, external() is implied.
*
* @return
*/
Builder& external() noexcept;
private:
friend class FTexture;
};
@@ -395,7 +407,7 @@ public:
/**
* Specify the external image to associate with this Texture. Typically the external
* Specify the external image to associate with this Texture. Typically, the external
* image is OS specific, and can be a video or camera frame.
* There are many restrictions when using an external image as a texture, such as:
* - only the level of detail (lod) 0 can be specified
@@ -420,7 +432,7 @@ public:
void setExternalImage(Engine& engine, void* UTILS_NONNULL image) noexcept;
/**
* Specify the external image and plane to associate with this Texture. Typically the external
* Specify the external image and plane to associate with this Texture. Typically, the external
* image is OS specific, and can be a video or camera frame. When using this method, the
* external image must be a planar type (such as a YUV camera frame). The plane parameter
* selects which image plane is bound to this texture.
+29 -21
View File
@@ -81,6 +81,7 @@ struct Texture::BuilderDetails {
Usage mUsage = Usage::NONE;
bool mHasBlitSrc = false;
bool mTextureIsSwizzled = false;
bool mExternal = false;
std::array<Swizzle, 4> mSwizzle = {
Swizzle::CHANNEL_0, Swizzle::CHANNEL_1,
Swizzle::CHANNEL_2, Swizzle::CHANNEL_3 };
@@ -136,6 +137,11 @@ Texture::Builder& Texture::Builder::import(intptr_t id) noexcept {
return *this;
}
Texture::Builder& Texture::Builder::external() noexcept {
mImpl->mExternal = true;
return *this;
}
Texture::Builder& Texture::Builder::swizzle(Swizzle r, Swizzle g, Swizzle b, Swizzle a) noexcept {
mImpl->mTextureIsSwizzled = true;
mImpl->mSwizzle = { r, g, b, a };
@@ -154,6 +160,11 @@ Texture* Texture::Builder::build(Engine& engine) {
(isProtectedTexturesSupported && useProtectedMemory) || !useProtectedMemory)
<< "Texture is PROTECTED but protected textures are not supported";
// SAMPLER_EXTERNAL implies imported.
if (mImpl->mTarget == SamplerType::SAMPLER_EXTERNAL) {
mImpl->mExternal = true;
}
uint8_t maxLevelCount;
switch (mImpl->mTarget) {
case SamplerType::SAMPLER_2D:
@@ -174,7 +185,7 @@ Texture* Texture::Builder::build(Engine& engine) {
mImpl->mUsage = TextureUsage::DEFAULT;
if (mImpl->mLevels > 1 &&
(mImpl->mWidth > 1 || mImpl->mHeight > 1) &&
mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) {
!mImpl->mExternal) {
const bool formatMipmappable =
downcast(engine).getDriverApi().isTextureFormatMipmappable(mImpl->mFormat);
if (formatMipmappable) {
@@ -243,9 +254,10 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) {
mSwizzle = builder->mSwizzle;
mTextureIsSwizzled = builder->mTextureIsSwizzled;
mHasBlitSrc = builder->mHasBlitSrc;
mExternal = builder->mExternal;
bool const isImported = builder->mImportedId != 0;
if (mTarget == SamplerType::SAMPLER_EXTERNAL && !isImported) {
if (mExternal && !isImported) {
// mHandle and mHandleForSampling will be created in setExternalImage()
// If this Texture is used for sampling before setExternalImage() is called,
// we'll lazily create a 1x1 placeholder texture.
@@ -318,8 +330,8 @@ void FTexture::setImage(FEngine& engine, size_t level,
<< "level=" << unsigned(level) << " is >= to levelCount=" << unsigned(mLevelCount)
<< ".";
FILAMENT_CHECK_PRECONDITION(mTarget != SamplerType::SAMPLER_EXTERNAL)
<< "Texture SamplerType::SAMPLER_EXTERNAL not supported for this operation.";
FILAMENT_CHECK_PRECONDITION(!mExternal)
<< "External Texture not supported for this operation.";
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1) << "Operation not supported with multisample ("
<< unsigned(mSampleCount) << ") texture.";
@@ -454,15 +466,14 @@ void FTexture::setImage(FEngine& engine, size_t level,
}
void FTexture::setExternalImage(FEngine& engine, void* image) noexcept {
if (mTarget != Sampler::SAMPLER_EXTERNAL) {
return;
}
FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external.";
// The call to setupExternalImage is synchronous, and allows the driver to take ownership of the
// external image on this thread, if necessary.
auto& api = engine.getDriverApi();
api.setupExternalImage(image);
auto texture = api.createTextureExternalImage(mFormat, mWidth, mHeight, mUsage, image);
auto texture = api.createTextureExternalImage(mTarget, mFormat, mWidth, mHeight, mUsage, image);
if (mTextureIsSwizzled) {
auto const& s = mSwizzle;
@@ -475,9 +486,8 @@ void FTexture::setExternalImage(FEngine& engine, void* image) noexcept {
}
void FTexture::setExternalImage(FEngine& engine, void* image, size_t plane) noexcept {
if (mTarget != Sampler::SAMPLER_EXTERNAL) {
return;
}
FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external.";
// The call to setupExternalImage is synchronous, and allows the driver to take ownership of
// the external image on this thread, if necessary.
auto& api = engine.getDriverApi();
@@ -497,9 +507,7 @@ void FTexture::setExternalImage(FEngine& engine, void* image, size_t plane) noex
}
void FTexture::setExternalStream(FEngine& engine, FStream* stream) noexcept {
if (mTarget != Sampler::SAMPLER_EXTERNAL) {
return;
}
FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external.";
auto& api = engine.getDriverApi();
auto texture = api.createTexture(
@@ -524,7 +532,7 @@ void FTexture::setExternalStream(FEngine& engine, FStream* stream) noexcept {
}
void FTexture::generateMipmaps(FEngine& engine) const noexcept {
FILAMENT_CHECK_PRECONDITION(mTarget != SamplerType::SAMPLER_EXTERNAL)
FILAMENT_CHECK_PRECONDITION(!mExternal)
<< "External Textures are not mipmappable.";
FILAMENT_CHECK_PRECONDITION(mTarget != SamplerType::SAMPLER_3D)
@@ -544,12 +552,11 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept {
}
bool FTexture::textureHandleCanMutate() const noexcept {
return (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) ||
mTarget == SamplerType::SAMPLER_EXTERNAL;
return (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) || mExternal;
}
void FTexture::updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept {
assert_invariant(mTarget != SamplerType::SAMPLER_EXTERNAL);
assert_invariant(!mExternal);
if (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) {
auto& range = mLodRange;
uint8_t const last = int8_t(baseLevel + levelCount);
@@ -601,7 +608,7 @@ backend::Handle<backend::HwTexture> FTexture::createPlaceholderTexture(
}
backend::Handle<backend::HwTexture> FTexture::getHwHandleForSampling() const noexcept {
if (UTILS_UNLIKELY(mTarget == SamplerType::SAMPLER_EXTERNAL && !mHandleForSampling)) {
if (UTILS_UNLIKELY(mExternal && !mHandleForSampling)) {
return setHandleForSampling(createPlaceholderTexture(*mDriver));
}
auto const& range = mLodRange;
@@ -610,9 +617,10 @@ backend::Handle<backend::HwTexture> FTexture::getHwHandleForSampling() const noe
if (UTILS_UNLIKELY(lodRangeChanged)) {
activeRange = range;
if (range.empty() || hasAllLods(range)) {
setHandleForSampling(mHandle);
std::ignore = setHandleForSampling(mHandle);
} else {
setHandleForSampling(mDriver->createTextureView(mHandle, range.first, range.size()));
std::ignore = setHandleForSampling(mDriver->createTextureView(
mHandle, range.first, range.size()));
}
}
return mHandleForSampling;
+2 -1
View File
@@ -173,7 +173,8 @@ private:
// Indicates whether the user has set the TextureUsage::BLIT_SRC usage. This will be used to
// temporarily validate whether this texture can be used for readPixels.
bool mHasBlitSrc = false;
// there is 5 bytes of padding here
bool mExternal = false;
// there is 4 bytes of padding here
FStream* mStream = nullptr; // only needed for streaming textures
};
+2
View File
@@ -1489,6 +1489,8 @@ class_<TexBuilder>("Texture$Builder")
return &builder->sampler(target); })
.BUILDER_FUNCTION("format", TexBuilder, (TexBuilder* builder, Texture::InternalFormat fmt), {
return &builder->format(fmt); })
.BUILDER_FUNCTION("external", TexBuilder, (TexBuilder* builder), {
return &builder->external(); })
// This takes a bitfield that can be composed by or'ing constants.
// - JS clients should use the value member, as in: "Texture$Usage.SAMPLEABLE.value".