Sitelet https://github.com/flutter/flutter/pull/190036/files
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

uniform sampler2DArray tex;

uniform FragInfo {
float layer;
}
frag_info;

in vec2 v_texture_coords;
in vec4 v_color;
out vec4 frag_color;

void main() {
frag_color = v_color * texture(tex, vec3(v_texture_coords, frag_info.layer));
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,17 @@ bool BlitCopyBufferToTextureCommandGLES::Encode(
texture_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
break;
case TextureType::kTexture2DArray:
// TODO(bdero): Upload into 2D array layers via glTexSubImage3D from the
// blit path. Direct uploads via Texture::SetContents are supported.
VALIDATION_LOG << "Blitting into a 2D array texture is not yet supported "
"on the OpenGLES backend.";
return false;
// The Flutter GPU Dart API gates array-texture creation on
// GpuContext.doesSupportTextureArrays, so this is a backstop for
// contexts without array support (e.g. OpenGL ES 2.0).
if (!reactor.GetProcTable().GetCapabilities()->SupportsTextureArrays()) {
VALIDATION_LOG
<< "2D array textures are not supported on this context.";
return false;
Comment on lines +189 to +191

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the sort of thing we should surface up to Dart. Can we add some TODO and file a follow up issue?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved directly instead of deferring: GpuContext.createTexture now throws when a texture2DArray is requested but doesSupportTextureArrays is false, so callers get a clear error at creation rather than a failed upload. It reads the cached capability at creation time only, nothing on the draw path. This engine check stays as a backstop. No follow-up issue needed. (8cfb9ef)

}
texture_type = GL_TEXTURE_2D_ARRAY;
texture_target = GL_TEXTURE_2D_ARRAY;
break;
case TextureType::kTextureExternalOES:
texture_type = GL_TEXTURE_EXTERNAL_OES;
texture_target = GL_TEXTURE_EXTERNAL_OES;
Expand Down Expand Up @@ -224,6 +230,14 @@ bool BlitCopyBufferToTextureCommandGLES::Encode(
// sub-image; glCompressedTexImage2D redefines the entire mip level. Require
// the upload to cover the full mip level starting at the origin.
if (gles_format->is_compressed) {
if (tex_descriptor.type == TextureType::kTexture2DArray) {
// TODO(bdero): Support compressed 2D array uploads. The level must be
// allocated with glCompressedTexImage3D covering every layer and filled
// per layer with glCompressedTexSubImage3D.
VALIDATION_LOG << "Compressed 2D array textures are not yet supported "
"on the OpenGLES backend.";
return false;
}
const auto mip_width =
std::max<int32_t>(1, tex_descriptor.size.width >> mip_level);
const auto mip_height =
Expand All @@ -248,6 +262,45 @@ bool BlitCopyBufferToTextureCommandGLES::Encode(
return true;
}

if (tex_descriptor.type == TextureType::kTexture2DArray) {
// glTexImage3D allocates this mip level for every layer at once, so the
// level's storage is tracked with a single entry (slice 0).
if (!texture_gles.IsSliceMipLevelInitialized(0, mip_level)) {
const auto level_width =
std::max<int32_t>(1, tex_descriptor.size.width >> mip_level);
const auto level_height =
std::max<int32_t>(1, tex_descriptor.size.height >> mip_level);
gl.TexImage3D(
/*target=*/texture_target, //
/*level=*/static_cast<GLint>(mip_level), //
/*internal_format=*/gles_format->internal_format, //
/*width=*/level_width, //
/*height=*/level_height, //
/*depth=*/
static_cast<GLsizei>(tex_descriptor.array_layer_count), //
/*border=*/0u, //
/*format=*/gles_format->external_format, //
/*type=*/gles_format->type, //
/*data=*/nullptr //
);
Comment thread
bdero marked this conversation as resolved.
texture_gles.MarkSliceMipLevelInitialized(0, mip_level);
}
gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1);
gl.TexSubImage3D(
/*target=*/texture_target, //
/*level=*/static_cast<GLint>(mip_level), //
/*xoffset=*/destination_region.GetX(), //
/*yoffset=*/destination_region.GetY(), //
/*zoffset=*/static_cast<GLint>(slice), //
/*width=*/destination_region.GetWidth(), //
/*height=*/destination_region.GetHeight(), //
/*depth=*/1, //
/*format=*/gles_format->external_format, //
/*type=*/gles_format->type, //
/*data=*/tex_data); //
Comment thread
bdero marked this conversation as resolved.
return true;
}

// GL_INVALID_OPERATION if the requested mip level has not been defined by
// a previous glTexImage2D operation. Allocate the requested mip lazily on
// first write, only for the level the upload is actually targeting. The
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ bool CapabilitiesGLES::SupportsTextureMaxLevel() const {
return supports_texture_max_level_;
}

bool CapabilitiesGLES::SupportsTextureArray() const {
bool CapabilitiesGLES::SupportsTextureArrays() const {
return supports_texture_array_;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ class CapabilitiesGLES final
/// available below them through GL_EXT_texture_array (desktop GL 2.x)
/// or GL_NV_texture_array (OpenGL ES 2.0). When absent, callers must
/// fall back to a texture atlas.
bool SupportsTextureArray() const;
// |Capabilities|
bool SupportsTextureArrays() const override;

// |Capabilities|
bool SupportsOffscreenMSAA() const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ TEST(CapabilitiesGLES, SupportsTextureArrayOnES3) {
// 2D array textures are core on OpenGL ES 3.0, no extension needed.
auto mock_gles = MockGLES::Init(std::nullopt, "OpenGL ES 3.0");
auto capabilities = mock_gles->GetProcTable().GetCapabilities();
EXPECT_TRUE(capabilities->SupportsTextureArray());
EXPECT_TRUE(capabilities->SupportsTextureArrays());
}

TEST(CapabilitiesGLES, DoesNotSupportTextureArrayOnES2WithoutExtension) {
auto const extensions = std::vector<const char*>{"GL_KHR_debug"};
auto mock_gles = MockGLES::Init(extensions, "OpenGL ES 2.0");
auto capabilities = mock_gles->GetProcTable().GetCapabilities();
EXPECT_FALSE(capabilities->SupportsTextureArray());
EXPECT_FALSE(capabilities->SupportsTextureArrays());
}

TEST(CapabilitiesGLES, SupportsTextureArrayViaNVExtensionOnES2) {
Expand All @@ -112,7 +112,7 @@ TEST(CapabilitiesGLES, SupportsTextureArrayViaNVExtensionOnES2) {
};
auto mock_gles = MockGLES::Init(extensions, "OpenGL ES 2.0");
auto capabilities = mock_gles->GetProcTable().GetCapabilities();
EXPECT_TRUE(capabilities->SupportsTextureArray());
EXPECT_TRUE(capabilities->SupportsTextureArrays());
}

TEST(CapabilitiesGLES, SupportsTextureArrayViaEXTExtension) {
Expand All @@ -124,7 +124,7 @@ TEST(CapabilitiesGLES, SupportsTextureArrayViaEXTExtension) {
};
auto mock_gles = MockGLES::Init(extensions, "OpenGL ES 2.0");
auto capabilities = mock_gles->GetProcTable().GetCapabilities();
EXPECT_TRUE(capabilities->SupportsTextureArray());
EXPECT_TRUE(capabilities->SupportsTextureArrays());
}

} // namespace testing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
#include "impeller/base/validation.h"
#include "impeller/core/buffer_view.h"
#include "impeller/core/formats.h"
#include "impeller/core/range.h"
#include "impeller/core/texture_descriptor.h"
#include "impeller/renderer/backend/gles/handle_gles.h"
#include "impeller/renderer/backend/gles/proc_table_gles.h"
#include "impeller/renderer/backend/gles/test/mock_gles.h"
#include "impeller/renderer/blit_pass.h"
#include "impeller/renderer/command_buffer.h"
#include "impeller/renderer/command_queue.h"

namespace impeller::testing {

Expand Down Expand Up @@ -155,24 +160,25 @@ TEST_P(TextureGLESTest, CanCreateAndUpload2DArrayTexture) {
if (!context_gles.GetReactor()
->GetProcTable()
.GetCapabilities()
->SupportsTextureArray()) {
->SupportsTextureArrays()) {
GTEST_SKIP() << "2D array textures are not supported on this context.";
}

// More than 6 layers, to cover slices beyond the cubemap face range.
TextureDescriptor desc;
desc.storage_mode = StorageMode::kHostVisible;
desc.size = {2, 2};
desc.format = PixelFormat::kR8G8B8A8UNormInt;
desc.type = TextureType::kTexture2DArray;
desc.array_layer_count = 3;
desc.array_layer_count = 8;
desc.mip_count = 1;

auto texture = GetContext()->GetResourceAllocator()->CreateTexture(desc);
ASSERT_TRUE(texture);
EXPECT_EQ(static_cast<int>(texture->GetTextureDescriptor().array_layer_count),
3);
EXPECT_TRUE(texture->IsSliceValid(2));
EXPECT_FALSE(texture->IsSliceValid(3));
8);
EXPECT_TRUE(texture->IsSliceValid(7));
EXPECT_FALSE(texture->IsSliceValid(8));

// Every layer can be uploaded.
std::vector<uint8_t> layer(2u * 2u * 4u, 0xFF);
Expand All @@ -181,6 +187,28 @@ TEST_P(TextureGLESTest, CanCreateAndUpload2DArrayTexture) {
EXPECT_TRUE(texture->SetContents(layer.data(), layer.size(), slice));
}
EXPECT_TRUE(context_gles.GetReactor()->React());

// Every layer can also be uploaded through the blit path.
auto staging = GetContext()->GetResourceAllocator()->CreateBufferWithCopy(
layer.data(), layer.size());
ASSERT_TRUE(staging);
auto command_buffer = GetContext()->CreateCommandBuffer();
ASSERT_TRUE(command_buffer);
auto blit_pass = command_buffer->CreateBlitPass();
ASSERT_TRUE(blit_pass);
for (size_t slice = 0; slice < static_cast<size_t>(desc.array_layer_count);
++slice) {
EXPECT_TRUE(blit_pass->AddCopy(BufferView(staging, Range(0, layer.size())),
texture, std::nullopt,
/*label=*/"ArrayLayerUpload",
/*mip_level=*/0, slice));
}
EXPECT_TRUE(blit_pass->EncodeCommands());
EXPECT_TRUE(GetContext()
->GetCommandQueue()
->Submit({std::move(command_buffer)})
.ok());
EXPECT_TRUE(context_gles.GetReactor()->React());
}

TEST_P(TextureGLESTest, CreatingAndBindingEmptyTexturesDoesNotCrash) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ bool TextureGLES::OnSetContents(std::shared_ptr<const fml::Mapping> mapping,
// Bail out synchronously on contexts without array support (e.g. ES 2.0).
// The glTexImage3D/glTexSubImage3D procs are null there, so queuing the
// upload would dereference a null proc on the reactor thread.
if (!reactor_->GetProcTable().GetCapabilities()->SupportsTextureArray()) {
if (!reactor_->GetProcTable().GetCapabilities()->SupportsTextureArrays()) {
VALIDATION_LOG << "2D array textures are not supported on this context.";
return false;
}
Expand Down Expand Up @@ -506,7 +506,7 @@ void TextureGLES::InitializeContentsIfNecessary() {
MarkSliceMipLevelInitialized(face, 0);
}
} else if (desc.type == TextureType::kTexture2DArray) {
if (!gl.GetCapabilities()->SupportsTextureArray()) {
if (!gl.GetCapabilities()->SupportsTextureArrays()) {
VALIDATION_LOG << "2D array textures are not supported on this "
"context.";
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,10 @@ bool CapabilitiesVK::SupportsManuallyMippedTextures() const {
return true;
}

bool CapabilitiesVK::SupportsTextureArrays() const {
return true;
}

void CapabilitiesVK::SetOffscreenFormat(PixelFormat pixel_format) const {
default_color_format_ = pixel_format;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ class CapabilitiesVK final : public Capabilities,
// |Capabilities|
bool SupportsManuallyMippedTextures() const override;

// |Capabilities|
bool SupportsTextureArrays() const override;

// |Capabilities|
bool SupportsExtendedRangeFormats() const override;

Expand Down
2 changes: 1 addition & 1 deletion engine/src/flutter/impeller/renderer/blit_pass.cc
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ bool BlitPass::AddCopy(BufferView source,
<< destination->GetMipCount() << " mip levels.";
return false;
}
if (slice > 5) {
if (!destination->IsSliceValid(slice)) {
VALIDATION_LOG << "Invalid value for slice: " << slice;
return false;
}
Expand Down
3 changes: 3 additions & 0 deletions engine/src/flutter/impeller/renderer/capabilities.cc
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ class StandardCapabilities final : public Capabilities {
// |Capabilities|
bool SupportsManuallyMippedTextures() const override { return true; }

// |Capabilities|
bool SupportsTextureArrays() const override { return true; }

// |Capabilities|
bool SupportsExtendedRangeFormats() const override {
return supports_extended_range_formats_;
Expand Down
6 changes: 6 additions & 0 deletions engine/src/flutter/impeller/renderer/capabilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ class Capabilities {
/// bounded to the levels the texture declares.
virtual bool SupportsManuallyMippedTextures() const = 0;

/// @brief Whether 2D array textures (`TextureType::kTexture2DArray`) can be
/// allocated and sampled. True everywhere except OpenGL ES 2.0
/// contexts without an array-texture extension, where callers must
/// fall back to a texture atlas.
virtual bool SupportsTextureArrays() const = 0;

/// @brief Returns a supported `PixelFormat` for textures that store
/// 4-channel colors (red/green/blue/alpha).
virtual PixelFormat GetDefaultColorFormat() const = 0;
Expand Down
2 changes: 2 additions & 0 deletions engine/src/flutter/impeller/renderer/testing/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ class MockCapabilities : public Capabilities {
MOCK_METHOD(bool, SupportsPrimitiveRestart, (), (const override));
MOCK_METHOD(bool, Supports32BitPrimitiveIndices, (), (const override));
MOCK_METHOD(bool, SupportsManuallyMippedTextures, (), (const override));

MOCK_METHOD(bool, SupportsTextureArrays, (), (const override));
MOCK_METHOD(bool, SupportsExtendedRangeFormats, (), (const override));
MOCK_METHOD(bool, SupportsFramebufferRenderMipmap, (), (const override));
MOCK_METHOD(bool,
Expand Down
13 changes: 13 additions & 0 deletions engine/src/flutter/lib/gpu/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ extern bool InternalFlutterGpu_Context_GetSupportsManuallyMippedTextures(
->SupportsManuallyMippedTextures();
}

extern bool InternalFlutterGpu_Context_GetSupportsTextureArrays(
flutter::gpu::Context* wrapper) {
// TODO(bdero): Shader bundles do not carry an OpenGL ES 3.0 shader variant
// yet, so user shaders cannot sample 2D array textures on the GLES backend
// even when the context itself supports them. Once the bundle format gains
// an ES 3.0 variant, gate on the context capability alone.
if (wrapper->GetContext().GetBackendType() ==
impeller::Context::BackendType::kOpenGLES) {
return false;
}
return wrapper->GetContext().GetCapabilities()->SupportsTextureArrays();
}

extern int InternalFlutterGpu_Context_GetMaxSamplerAnisotropy(
flutter::gpu::Context* wrapper) {
return wrapper->GetContext().GetCapabilities()->GetMaxSamplerAnisotropy();
Expand Down
4 changes: 4 additions & 0 deletions engine/src/flutter/lib/gpu/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ FLUTTER_GPU_EXPORT
extern bool InternalFlutterGpu_Context_GetSupportsManuallyMippedTextures(
flutter::gpu::Context* wrapper);

FLUTTER_GPU_EXPORT
extern bool InternalFlutterGpu_Context_GetSupportsTextureArrays(
flutter::gpu::Context* wrapper);

FLUTTER_GPU_EXPORT
extern int InternalFlutterGpu_Context_GetMaxSamplerAnisotropy(
flutter::gpu::Context* wrapper);
Expand Down
44 changes: 44 additions & 0 deletions engine/src/flutter/lib/gpu/formats.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,50 @@ constexpr FlutterGPUStorageMode FromImpellerStorageMode(
}
}

enum class FlutterGPUTextureType {
kTexture2D,
kTexture2DMultisample,
kTextureCube,
kTextureExternalOES,
kTexture2DArray,
};

constexpr impeller::TextureType ToImpellerTextureType(
FlutterGPUTextureType value) {
switch (value) {
case FlutterGPUTextureType::kTexture2D:
return impeller::TextureType::kTexture2D;
case FlutterGPUTextureType::kTexture2DMultisample:
return impeller::TextureType::kTexture2DMultisample;
case FlutterGPUTextureType::kTextureCube:
return impeller::TextureType::kTextureCube;
case FlutterGPUTextureType::kTextureExternalOES:
return impeller::TextureType::kTextureExternalOES;
case FlutterGPUTextureType::kTexture2DArray:
return impeller::TextureType::kTexture2DArray;
}
}

constexpr impeller::TextureType ToImpellerTextureType(int value) {
return ToImpellerTextureType(static_cast<FlutterGPUTextureType>(value));
}

constexpr FlutterGPUTextureType FromImpellerTextureType(
impeller::TextureType value) {
switch (value) {
case impeller::TextureType::kTexture2D:
return FlutterGPUTextureType::kTexture2D;
case impeller::TextureType::kTexture2DMultisample:
return FlutterGPUTextureType::kTexture2DMultisample;
case impeller::TextureType::kTextureCube:
return FlutterGPUTextureType::kTextureCube;
case impeller::TextureType::kTextureExternalOES:
return FlutterGPUTextureType::kTextureExternalOES;
case impeller::TextureType::kTexture2DArray:
return FlutterGPUTextureType::kTexture2DArray;
}
}

enum class FlutterGPUPixelFormat {
kUnknown,
kA8UNormInt,
Expand Down
Loading
Loading