blob: e58e56aa8fbd735c6afdcc0bfa0a061bf273ef53 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <zircon/availability.h>
// This file is always in the GN sources list, but its contents should not be
// used for API levels other than HEAD where images2 and sysmem2 are supported.
#if __Fuchsia_API_level__ < 19
// Enable a subset of functionality. See https://fxbug.dev/42085119.
// It cannot be undefined because other Fuchsia headers may indirectly include image_format.h.
#define __ALLOW_IMAGES2_AND_SYSMEM2_TYPES_ONLY__
#endif
#include <fidl/fuchsia.images2/cpp/fidl.h>
#include <fidl/fuchsia.sysmem2/cpp/fidl.h>
#include "lib/image-format/image_format.h"
#if defined(FIDL_ALLOW_DEPRECATED_C_BINDINGS)
#include <fuchsia/sysmem/c/fidl.h>
#endif
#include <lib/fidl/cpp/wire_natural_conversions.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <lib/zbi-format/graphics.h>
#include <zircon/assert.h>
#include <algorithm>
#include <map>
#include <set>
#include <fbl/algorithm.h>
#include <safemath/safe_math.h>
using safemath::CheckAdd;
using safemath::CheckDiv;
using safemath::CheckMul;
using safemath::CheckSub;
#if __Fuchsia_API_level__ < 19
// This is used by the ImageFormatSet implementations before it is defined.
// Normally, it is declared by the header.
uint32_t ImageFormatStrideBytesPerWidthPixel(const PixelFormatAndModifier& pixel_format);
#endif
namespace {
using ColorSpace = fuchsia_images2::ColorSpace;
using ImageFormat = fuchsia_images2::ImageFormat;
using ImageFormatConstraints = fuchsia_sysmem2::ImageFormatConstraints;
using PixelFormat = fuchsia_images2::PixelFormat;
using ColorSpaceWire = fuchsia_images2::wire::ColorSpace;
using ImageFormatWire = fuchsia_images2::wire::ImageFormat;
using ImageFormatConstraintsWire = fuchsia_sysmem2::wire::ImageFormatConstraints;
using PixelFormatWire = fuchsia_images2::wire::PixelFormat;
#if __Fuchsia_API_level__ >= 19
// There are two aspects of the ColorSpaceWire and PixelFormatWire that we care about:
// * bits-per-sample - bits per primary sample (R, G, B, or Y)
// * RGB vs. YUV - whether the system supports the ColorSpaceWire or PixelFormatWire
// representing RGB data or YUV data. Any given ColorSpaceWire only supports
// one or the other. Currently any given PixelFormatWire only supports one or
// the other and this isn't likely to change.
// While we could just list all the ColorSpaceWire(s) that each PixelFormatWire could
// plausibly support, expressing in terms of bits-per-sample and RGB vs. YUV is
// perhaps easier to grok.
enum ColorType { kColorType_NONE, kColorType_RGB, kColorType_YUV };
struct SamplingInfo {
std::set<uint32_t> possible_bits_per_sample;
std::set<ColorType> color_types;
};
const std::map<ColorSpaceWire, SamplingInfo> kColorSpaceSamplingInfo = {
{ColorSpace::kSrgb, {{8, 10, 12, 16}, {kColorType_RGB}}},
{ColorSpace::kRec601Ntsc, {{8, 10}, {kColorType_YUV}}},
{ColorSpace::kRec601NtscFullRange, {{8, 10}, {kColorType_YUV}}},
{ColorSpace::kRec601Pal, {{8, 10}, {kColorType_YUV}}},
{ColorSpace::kRec601PalFullRange, {{8, 10}, {kColorType_YUV}}},
{ColorSpace::kRec709, {{8, 10}, {kColorType_YUV}}},
{ColorSpace::kRec2020, {{10, 12}, {kColorType_YUV}}},
{ColorSpace::kRec2100, {{10, 12}, {kColorType_YUV}}},
};
const std::map<PixelFormatWire, SamplingInfo> kPixelFormatSamplingInfo = {
{PixelFormat::kR8G8B8A8, {{8}, {kColorType_RGB}}},
{PixelFormat::kR8G8B8X8, {{8}, {kColorType_RGB}}},
{PixelFormat::kB8G8R8A8, {{8}, {kColorType_RGB}}},
{PixelFormat::kB8G8R8X8, {{8}, {kColorType_RGB}}},
{PixelFormat::kI420, {{8}, {kColorType_YUV}}},
{PixelFormat::kM420, {{8}, {kColorType_YUV}}},
{PixelFormat::kNv12, {{8}, {kColorType_YUV}}},
{PixelFormat::kP010, {{10}, {kColorType_YUV}}},
{PixelFormat::kYuy2, {{8}, {kColorType_YUV}}},
// 8 bits RGB when uncompressed - in this context, MJPEG is essentially
// pretending to be uncompressed.
{PixelFormat::kMjpeg, {{8}, {kColorType_RGB}}},
{PixelFormat::kYv12, {{8}, {kColorType_YUV}}},
{PixelFormat::kB8G8R8, {{8}, {kColorType_RGB}}},
{PixelFormat::kR8G8B8, {{8}, {kColorType_RGB}}},
// These use the same colorspaces as regular 8-bit-per-component formats
{PixelFormat::kR5G6B5, {{8}, {kColorType_RGB}}},
{PixelFormat::kR3G3B2, {{8}, {kColorType_RGB}}},
{PixelFormat::kR2G2B2X2, {{8}, {kColorType_RGB}}},
// Expands to RGB or YUV
{PixelFormat::kL8, {{8}, {kColorType_RGB, kColorType_YUV}}},
{PixelFormat::kR8, {{8}, {kColorType_RGB, kColorType_YUV}}},
{PixelFormat::kR8G8, {{8}, {kColorType_RGB}}},
{PixelFormat::kA2B10G10R10, {{8}, {kColorType_RGB}}},
{PixelFormat::kA2R10G10B10, {{8}, {kColorType_RGB}}},
};
#endif // __Fuchsia_API_level__ >= 19
constexpr uint32_t kTransactionEliminationAlignment = 64;
// The transaction elimination buffer is always reported as plane 3.
constexpr uint32_t kTransactionEliminationPlane = 3;
uint64_t arm_transaction_elimination_row_size(uint32_t width) {
uint32_t kTileSize = 32;
uint32_t kBytesPerTilePerRow = 16;
uint32_t width_in_tiles = fbl::round_up(width, kTileSize) / kTileSize;
return fbl::round_up(width_in_tiles * kBytesPerTilePerRow, kTransactionEliminationAlignment);
}
uint64_t arm_transaction_elimination_buffer_size(uint64_t start, uint32_t width, uint32_t height) {
uint32_t kTileSize = 32;
uint32_t end = start;
end = fbl::round_up(end, kTransactionEliminationAlignment);
uint32_t kHeaderSize = kTransactionEliminationAlignment;
end += kHeaderSize;
uint32_t height_in_tiles = fbl::round_up(height, kTileSize) / kTileSize;
end += arm_transaction_elimination_row_size(width) * 2 * height_in_tiles;
return end - start;
}
class ImageFormatSet {
public:
virtual const char* Name() const = 0;
virtual bool IsSupported(const PixelFormatAndModifier& pixel_format) const = 0;
virtual uint64_t ImageFormatImageSize(const ImageFormat& image_format) const = 0;
virtual bool ImageFormatPlaneByteOffset(const ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) const = 0;
virtual bool ImageFormatPlaneRowBytes(const ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) const = 0;
virtual bool ImageFormatMinimumRowBytes(
const fuchsia_sysmem2::ImageFormatConstraints& constraints, uint32_t width,
uint32_t* minimum_row_bytes_out) const = 0;
};
class IntelTiledFormats : public ImageFormatSet {
public:
const char* Name() const override { return "IntelTiledFormats"; }
bool IsSupported(const PixelFormatAndModifier& pixel_format) const override {
if (pixel_format.pixel_format != PixelFormat::kR8G8B8A8 &&
pixel_format.pixel_format != PixelFormat::kR8G8B8X8 &&
pixel_format.pixel_format != PixelFormat::kB8G8R8A8 &&
pixel_format.pixel_format != PixelFormat::kB8G8R8X8 &&
pixel_format.pixel_format != PixelFormat::kNv12) {
return false;
}
switch (pixel_format.pixel_format_modifier) {
case fuchsia_images2::PixelFormatModifier::kIntelI915XTiled:
case fuchsia_images2::PixelFormatModifier::kIntelI915YTiled:
case fuchsia_images2::PixelFormatModifier::kIntelI915YfTiled:
// X-Tiled CCS is not supported.
case fuchsia_images2::PixelFormatModifier::kIntelI915YTiledCcs:
case fuchsia_images2::PixelFormatModifier::kIntelI915YfTiledCcs:
return true;
default:
return false;
}
}
uint64_t ImageFormatImageSize(const ImageFormat& image_format) const override {
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
ZX_DEBUG_ASSERT(IsSupported(PixelFormatAndModifier(pixel_format_and_modifier)));
uint32_t width_in_tiles, height_in_tiles;
uint32_t num_of_planes = FormatNumOfPlanes(image_format.pixel_format().value());
uint64_t size = 0u;
for (uint32_t plane_idx = 0; plane_idx < num_of_planes; plane_idx += 1) {
GetSizeInTiles(image_format, plane_idx, &width_in_tiles, &height_in_tiles);
size += (width_in_tiles * height_in_tiles * kIntelTileByteSize);
}
if (FormatHasCcs(pixel_format_and_modifier)) {
size += CcsSize(width_in_tiles, height_in_tiles);
}
return size;
}
bool ImageFormatPlaneByteOffset(const ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) const override {
ZX_DEBUG_ASSERT(IsSupported(PixelFormatAndModifier(
image_format.pixel_format().value(), image_format.pixel_format_modifier().value())));
uint32_t num_of_planes = FormatNumOfPlanes(image_format.pixel_format().value());
uint32_t end_plane;
// For image data planes, calculate the size of all previous the image data planes
if (plane < num_of_planes) {
end_plane = plane;
} else if (plane == kCcsPlane) { // If requesting the CCS Aux plane, calculate the size of all
// the image data planes
end_plane = num_of_planes;
} else { // Plane is out of bounds, return false
return false;
}
uint64_t offset = 0u;
for (uint32_t plane_idx = 0u; plane_idx < end_plane; plane_idx += 1u) {
uint32_t width_in_tiles, height_in_tiles;
GetSizeInTiles(image_format, plane_idx, &width_in_tiles, &height_in_tiles);
offset += (width_in_tiles * height_in_tiles * kIntelTileByteSize);
}
ZX_DEBUG_ASSERT(offset % kIntelTileByteSize == 0);
*offset_out = offset;
return true;
}
bool ImageFormatPlaneRowBytes(const ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) const override {
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
ZX_DEBUG_ASSERT(IsSupported(pixel_format_and_modifier));
uint32_t num_of_planes = FormatNumOfPlanes(image_format.pixel_format().value());
if (plane < num_of_planes) {
uint32_t width_in_tiles, height_in_tiles;
GetSizeInTiles(image_format, plane, &width_in_tiles, &height_in_tiles);
const auto& tiling_data = GetTilingData(GetTilingTypeForPixelFormat(PixelFormatAndModifier(
image_format.pixel_format().value(), image_format.pixel_format_modifier().value())));
*row_bytes_out = width_in_tiles * tiling_data.bytes_per_row_per_tile;
return true;
}
if (plane == kCcsPlane && FormatHasCcs(pixel_format_and_modifier)) {
uint32_t width_in_tiles, height_in_tiles;
// Since we only care about the width, just use the first plane
GetSizeInTiles(image_format, 0, &width_in_tiles, &height_in_tiles);
*row_bytes_out =
CcsWidthInTiles(width_in_tiles) * GetTilingData(TilingType::kY).bytes_per_row_per_tile;
return true;
}
return false;
}
bool ImageFormatMinimumRowBytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) const override {
auto pixel_format_and_modifier = PixelFormatAndModifierFromConstraints(constraints);
ZX_DEBUG_ASSERT(IsSupported(pixel_format_and_modifier));
ZX_DEBUG_ASSERT(minimum_row_bytes_out);
// Caller must set pixel_format.
ZX_DEBUG_ASSERT(constraints.pixel_format().has_value());
if (constraints.size_alignment().has_value()) {
width = fbl::round_up(width, constraints.size_alignment()->width());
}
if ((constraints.min_size().has_value() && width < constraints.min_size()->width()) ||
(constraints.max_size().has_value() && width > constraints.max_size()->width())) {
return false;
}
uint32_t constraints_min_bytes_per_row =
constraints.min_bytes_per_row().has_value() ? constraints.min_bytes_per_row().value() : 0;
uint32_t constraints_bytes_per_row_divisor = constraints.bytes_per_row_divisor().has_value()
? constraints.bytes_per_row_divisor().value()
: 1;
const auto& tiling_data = GetTilingData(GetTilingTypeForPixelFormat(pixel_format_and_modifier));
constraints_bytes_per_row_divisor =
fbl::round_up(constraints_bytes_per_row_divisor, tiling_data.bytes_per_row_per_tile);
// This code should match the code in garnet/public/rust/fuchsia-framebuffer/src/sysmem.rs.
uint32_t non_padding_bytes_per_row;
if (!CheckMul(ImageFormatStrideBytesPerWidthPixel(pixel_format_and_modifier), width)
.AssignIfValid(&non_padding_bytes_per_row)) {
return false;
}
*minimum_row_bytes_out =
fbl::round_up(std::max(non_padding_bytes_per_row, constraints_min_bytes_per_row),
constraints_bytes_per_row_divisor);
if (constraints.max_bytes_per_row().has_value() &&
*minimum_row_bytes_out > constraints.max_bytes_per_row().value()) {
return false;
}
return true;
}
private:
struct TilingData {
uint32_t tile_rows;
uint32_t bytes_per_row_per_tile;
};
// These are base Intel tilings, with no aux buffers.
enum class TilingType { kX, kY, kYf };
// See
// https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-skl-vol05-memory_views.pdf
static constexpr uint32_t kIntelTileByteSize = 4096;
static constexpr TilingData kTilingData[] = {
{
// kX
.tile_rows = 8,
.bytes_per_row_per_tile = 512,
},
{
// kY
.tile_rows = 32,
.bytes_per_row_per_tile = 128,
},
{
// kYf
.tile_rows = 32,
.bytes_per_row_per_tile = 128,
},
};
// For simplicity CCS plane is always 3, leaving room for Y, U, and V planes if the format is I420
// or similar.
static constexpr uint32_t kCcsPlane = 3;
// See https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-kbl-vol12-display.pdf
// for a description of the color control surface. The CCS is always Y-tiled. A CCS cache-line
// (64 bytes, so 2 fit horizontally in a tile) represents 16 horizontal cache line pairs (so 16
// tiles) and 16 pixels tall.
static constexpr uint32_t kCcsTileWidthRatio = 2 * 16;
static constexpr uint32_t kCcsTileHeightRatio = 16;
static TilingType GetTilingTypeForPixelFormat(const PixelFormatAndModifier& pixel_format) {
auto modifier_without_ccs_bit = static_cast<fuchsia_images2::PixelFormatModifier>(
fidl::ToUnderlying(pixel_format.pixel_format_modifier) &
~fuchsia_images2::kFormatModifierIntelCcsBit);
switch (modifier_without_ccs_bit) {
case fuchsia_images2::PixelFormatModifier::kIntelI915XTiled:
return TilingType::kX;
case fuchsia_images2::PixelFormatModifier::kIntelI915YTiled:
return TilingType::kY;
case fuchsia_images2::PixelFormatModifier::kIntelI915YfTiled:
return TilingType::kYf;
default:
ZX_DEBUG_ASSERT(false);
return TilingType::kX;
}
}
static const TilingData& GetTilingData(TilingType type) {
static_assert(static_cast<size_t>(TilingType::kYf) < std::size(kTilingData));
ZX_DEBUG_ASSERT(static_cast<uint32_t>(type) < std::size(kTilingData));
return kTilingData[static_cast<uint32_t>(type)];
}
// Gets the total size (in tiles) of image data for non-aux planes
static void GetSizeInTiles(const ImageFormat& image_format, uint32_t plane, uint32_t* width_out,
uint32_t* height_out) {
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
const auto& tiling_data = GetTilingData(GetTilingTypeForPixelFormat(pixel_format_and_modifier));
ZX_DEBUG_ASSERT(image_format.bytes_per_row().has_value());
uint32_t bytes_per_row = image_format.bytes_per_row().value();
const auto& bytes_per_row_per_tile = tiling_data.bytes_per_row_per_tile;
const auto& tile_rows = tiling_data.tile_rows;
switch (pixel_format_and_modifier.pixel_format) {
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8: {
// Format only has one plane
ZX_DEBUG_ASSERT(plane == 0);
*width_out = fbl::round_up(bytes_per_row, bytes_per_row_per_tile) / bytes_per_row_per_tile;
*height_out = fbl::round_up(image_format.size()->height(), tile_rows) / tile_rows;
} break;
// Since NV12 is a biplanar format we must handle the size for each plane separately. From
// https://github.com/intel/gmmlib/blob/e1f634c5d5a41ac48756b25697ea499605711747/Source/GmmLib/Texture/GmmTextureAlloc.cpp#L1192:
// "For Tiled Planar surfaces, the planes must be tile-boundary aligned." Meaning that each
// plane must be separately tiled aligned.
case PixelFormat::kNv12:
if (plane == 0) {
// Calculate the Y plane size (8 bpp)
*width_out =
fbl::round_up(bytes_per_row, bytes_per_row_per_tile) / bytes_per_row_per_tile;
*height_out = fbl::round_up(image_format.size()->height(), tile_rows) / tile_rows;
} else if (plane == 1) {
// Calculate the UV plane size (4 bpp)
// We effectively have 1/2 the height of our original image since we are subsampled at
// 4:2:0. Since width of the Y plane must match the width of the UV plane we divide the
// height of the Y plane by 2 to calculate the height of the UV plane (aligned on tile
// height boundaries). Ensure the height is aligned 2 before dividing.
uint32_t adjusted_height = fbl::round_up(image_format.size()->height(), 2u) / 2u;
*width_out =
fbl::round_up(bytes_per_row, bytes_per_row_per_tile) / bytes_per_row_per_tile;
*height_out = fbl::round_up(adjusted_height, tile_rows) / tile_rows;
} else {
ZX_DEBUG_ASSERT(false);
}
break;
default:
ZX_DEBUG_ASSERT(false);
return;
}
}
static bool FormatHasCcs(const PixelFormatAndModifier& pixel_format) {
return fidl::ToUnderlying(pixel_format.pixel_format_modifier) &
fuchsia_images2::kFormatModifierIntelCcsBit;
}
// Does not include aux planes
static uint32_t FormatNumOfPlanes(const PixelFormat& pixel_format) {
switch (pixel_format) {
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
return 1u;
case PixelFormat::kNv12:
return 2u;
default:
ZX_DEBUG_ASSERT(false);
return 0u;
}
}
static uint64_t CcsWidthInTiles(uint32_t main_plane_width_in_tiles) {
return fbl::round_up(main_plane_width_in_tiles, kCcsTileWidthRatio) / kCcsTileWidthRatio;
}
static uint64_t CcsSize(uint32_t width_in_tiles, uint32_t height_in_tiles) {
uint32_t height_in_ccs_tiles =
fbl::round_up(height_in_tiles, kCcsTileHeightRatio) / kCcsTileHeightRatio;
return CcsWidthInTiles(width_in_tiles) * height_in_ccs_tiles * kIntelTileByteSize;
}
};
class AfbcFormats : public ImageFormatSet {
public:
const char* Name() const override { return "AfbcFormats"; }
static constexpr uint64_t kAfbcModifierMask =
fuchsia_images2::kFormatModifierArmTeBit | fuchsia_images2::kFormatModifierArmSplitBlockBit |
fuchsia_images2::kFormatModifierArmSparseBit | fuchsia_images2::kFormatModifierArmYuvBit |
fuchsia_images2::kFormatModifierArmBchBit | fuchsia_images2::kFormatModifierArmTiledHeaderBit;
bool IsSupported(const PixelFormatAndModifier& pixel_format) const override {
if (pixel_format.pixel_format != PixelFormatWire::kR8G8B8A8 &&
pixel_format.pixel_format != PixelFormatWire::kR8G8B8X8 &&
pixel_format.pixel_format != PixelFormatWire::kB8G8R8A8 &&
pixel_format.pixel_format != PixelFormatWire::kB8G8R8X8) {
return false;
}
auto modifier_without_afbc = static_cast<fuchsia_images2::PixelFormatModifier>(
fidl::ToUnderlying(pixel_format.pixel_format_modifier) & ~kAfbcModifierMask);
switch (modifier_without_afbc) {
case fuchsia_images2::PixelFormatModifier::kArmAfbc16X16:
case fuchsia_images2::PixelFormatModifier::kArmAfbc32X8:
return true;
default:
return false;
}
}
// Calculate the size of the Raw AFBC image without a transaction elimination buffer.
uint64_t NonTESize(const ImageFormat& image_format) const {
// See
// https://android.googlesource.com/device/linaro/hikey/+/android-o-preview-3/gralloc960/alloc_device.cpp
constexpr uint32_t kAfbcBodyAlignment = 1024u;
constexpr uint32_t kTiledAfbcBodyAlignment = 4096u;
ZX_DEBUG_ASSERT(image_format.pixel_format().has_value());
ZX_DEBUG_ASSERT(image_format.pixel_format_modifier().has_value());
ZX_DEBUG_ASSERT(IsSupported(PixelFormatAndModifier(
image_format.pixel_format().value(), image_format.pixel_format_modifier().value())));
uint32_t block_width;
uint32_t block_height;
uint32_t width_alignment;
uint32_t height_alignment;
bool tiled_header = fidl::ToUnderlying(image_format.pixel_format_modifier().value()) &
fuchsia_images2::kFormatModifierArmTiledHeaderBit;
auto modifier_without_afbc = static_cast<fuchsia_images2::PixelFormatModifier>(
fidl::ToUnderlying(image_format.pixel_format_modifier().value()) & ~kAfbcModifierMask);
switch (modifier_without_afbc) {
case fuchsia_images2::PixelFormatModifier::kArmAfbc16X16:
block_width = 16;
block_height = 16;
if (!tiled_header) {
width_alignment = block_width;
height_alignment = block_height;
} else {
width_alignment = 128;
height_alignment = 128;
}
break;
case fuchsia_images2::PixelFormatModifier::kArmAfbc32X8:
block_width = 32;
block_height = 8;
if (!tiled_header) {
width_alignment = block_width;
height_alignment = block_height;
} else {
width_alignment = 256;
height_alignment = 64;
}
break;
default:
return 0;
}
uint32_t body_alignment = tiled_header ? kTiledAfbcBodyAlignment : kAfbcBodyAlignment;
ZX_DEBUG_ASSERT(image_format.pixel_format().has_value());
ZX_DEBUG_ASSERT(image_format.pixel_format().value() == PixelFormatWire::kR8G8B8A8 ||
image_format.pixel_format().value() == PixelFormatWire::kR8G8B8X8 ||
image_format.pixel_format().value() == PixelFormatWire::kB8G8R8A8 ||
image_format.pixel_format().value() == PixelFormatWire::kB8G8R8X8);
constexpr uint32_t kBytesPerPixel = 4;
constexpr uint32_t kBytesPerBlockHeader = 16;
ZX_DEBUG_ASSERT(image_format.size().has_value());
uint64_t block_count =
fbl::round_up(image_format.size()->width(), width_alignment) / block_width *
fbl::round_up(image_format.size()->height(), height_alignment) / block_height;
return block_count * block_width * block_height * kBytesPerPixel +
fbl::round_up(block_count * kBytesPerBlockHeader, body_alignment);
}
uint64_t ImageFormatImageSize(const ImageFormat& image_format) const override {
uint64_t size = NonTESize(image_format);
if (fidl::ToUnderlying(image_format.pixel_format_modifier().value()) &
fuchsia_images2::kFormatModifierArmTeBit) {
size += arm_transaction_elimination_buffer_size(size, image_format.size()->width(),
image_format.size()->height());
}
return size;
}
bool ImageFormatPlaneByteOffset(const ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) const override {
ZX_DEBUG_ASSERT(IsSupported(PixelFormatAndModifier(
image_format.pixel_format().value(), image_format.pixel_format_modifier().value())));
if (plane == 0) {
*offset_out = 0;
return true;
}
if (plane == kTransactionEliminationPlane) {
*offset_out = fbl::round_up(NonTESize(image_format), kTransactionEliminationAlignment);
return true;
}
return false;
}
bool ImageFormatPlaneRowBytes(const ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) const override {
if (plane == 0) {
*row_bytes_out = 0;
return true;
}
if (plane == kTransactionEliminationPlane) {
*row_bytes_out = arm_transaction_elimination_row_size(image_format.size()->width());
return true;
}
return false;
}
bool ImageFormatMinimumRowBytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) const override {
ZX_DEBUG_ASSERT(IsSupported(PixelFormatAndModifier(
constraints.pixel_format().value(), constraints.pixel_format_modifier().value())));
if (constraints.min_size().has_value() && width < constraints.min_size()->width()) {
return false;
}
if (constraints.max_size().has_value() && width > constraints.max_size()->width()) {
return false;
}
uint32_t block_width;
uint32_t width_alignment;
bool tiled_header = fidl::ToUnderlying(constraints.pixel_format_modifier().value()) &
fuchsia_images2::kFormatModifierArmTiledHeaderBit;
auto modifier_without_afbc = static_cast<fuchsia_images2::PixelFormatModifier>(
fidl::ToUnderlying(constraints.pixel_format_modifier().value()) & ~kAfbcModifierMask);
switch (modifier_without_afbc) {
case fuchsia_images2::PixelFormatModifier::kArmAfbc16X16:
block_width = 16;
if (!tiled_header) {
width_alignment = block_width;
} else {
width_alignment = 128;
}
break;
case fuchsia_images2::PixelFormatModifier::kArmAfbc32X8:
block_width = 32;
if (!tiled_header) {
width_alignment = block_width;
} else {
width_alignment = 256;
}
break;
default:
return false;
}
// Divide with round up instead of down: (width + (width_alignment - 1)) / width_alignment
auto width_in_blocks = CheckDiv(CheckAdd(width, CheckSub(width_alignment, 1)), width_alignment);
auto width_in_pixels = CheckMul(width_in_blocks, block_width);
constexpr uint32_t kBytesPerPixel = 4;
auto width_in_bytes = CheckMul(width_in_pixels, kBytesPerPixel);
if (!width_in_bytes.AssignIfValid(minimum_row_bytes_out)) {
return false;
}
return true;
}
};
uint64_t linear_size(uint32_t surface_height, uint32_t bytes_per_row, PixelFormat type) {
switch (type) {
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
case PixelFormat::kR5G6B5:
case PixelFormat::kR3G3B2:
case PixelFormat::kR2G2B2X2:
case PixelFormat::kL8:
case PixelFormat::kR8:
case PixelFormat::kR8G8:
case PixelFormat::kA2B10G10R10:
case PixelFormat::kA2R10G10B10:
return surface_height * bytes_per_row;
case PixelFormat::kI420:
return surface_height * bytes_per_row * 3 / 2;
case PixelFormat::kM420:
return surface_height * bytes_per_row * 3 / 2;
case PixelFormat::kNv12:
return surface_height * bytes_per_row * 3 / 2;
case PixelFormat::kP010:
return surface_height * bytes_per_row * 3 / 2;
case PixelFormat::kYuy2:
return surface_height * bytes_per_row;
case PixelFormat::kYv12:
return surface_height * bytes_per_row * 3 / 2;
default:
return 0u;
}
}
bool linear_minimum_row_bytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) {
ZX_DEBUG_ASSERT(minimum_row_bytes_out);
// Caller must set pixel_format.
ZX_DEBUG_ASSERT(constraints.pixel_format().has_value());
if ((constraints.min_size().has_value() && width < constraints.min_size()->width()) ||
(constraints.max_size().has_value() && width > constraints.max_size()->width())) {
return false;
}
// We don't enforce that width is already aligned up by the caller by the time of this call, but
// if the caller is a producer, the caller is nonetheless expected to conform to the
// size_alignment.width divisibility requirement for any fuchsia.sysmem.ImageFormat2.coded_width
// or fuchsia.images2.ImageFormat.size.width values the caller generates.
if (constraints.size_alignment().has_value()) {
width = fbl::round_up(width, constraints.size_alignment()->width());
}
uint32_t constraints_min_bytes_per_row =
constraints.min_bytes_per_row().has_value() ? constraints.min_bytes_per_row().value() : 0;
uint32_t constraints_bytes_per_row_divisor = constraints.bytes_per_row_divisor().has_value()
? constraints.bytes_per_row_divisor().value()
: 1;
// This code should match the code in garnet/public/rust/fuchsia-framebuffer/src/sysmem.rs.
*minimum_row_bytes_out = fbl::round_up(
std::max(
ImageFormatStrideBytesPerWidthPixel(PixelFormatAndModifierFromConstraints(constraints)) *
width,
constraints_min_bytes_per_row),
constraints_bytes_per_row_divisor);
if (constraints.max_bytes_per_row().has_value() &&
*minimum_row_bytes_out > constraints.max_bytes_per_row().value()) {
return false;
}
return true;
}
class LinearFormats : public ImageFormatSet {
public:
const char* Name() const override { return "LinearFormats"; }
bool IsSupported(const PixelFormatAndModifier& pixel_format) const override {
if (pixel_format.pixel_format_modifier != fuchsia_images2::PixelFormatModifier::kLinear) {
return false;
}
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
return false;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
case PixelFormat::kI420:
case PixelFormat::kM420:
case PixelFormat::kNv12:
case PixelFormat::kP010:
case PixelFormat::kYuy2:
case PixelFormat::kYv12:
case PixelFormat::kR5G6B5:
case PixelFormat::kR3G3B2:
case PixelFormat::kR2G2B2X2:
case PixelFormat::kL8:
case PixelFormat::kR8:
case PixelFormat::kR8G8:
case PixelFormat::kA2B10G10R10:
case PixelFormat::kA2R10G10B10:
return true;
default:
return false;
}
return false;
}
uint64_t ImageFormatImageSize(const ImageFormat& image_format) const override {
ZX_DEBUG_ASSERT(image_format.pixel_format().has_value());
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
ZX_DEBUG_ASSERT(IsSupported(pixel_format_and_modifier));
ZX_DEBUG_ASSERT(image_format.size().has_value());
ZX_DEBUG_ASSERT(image_format.bytes_per_row().has_value());
uint32_t surface_height = image_format.size()->height();
uint32_t bytes_per_row =
image_format.bytes_per_row().has_value() ? image_format.bytes_per_row().value() : 0;
return linear_size(surface_height, bytes_per_row, pixel_format_and_modifier.pixel_format);
}
bool ImageFormatPlaneByteOffset(const ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) const override {
if (plane == 0) {
*offset_out = 0;
return true;
}
if (plane == 1) {
switch (image_format.pixel_format().value()) {
case PixelFormat::kP010:
case PixelFormat::kNv12:
case PixelFormat::kI420:
case PixelFormat::kYv12: {
*offset_out =
CheckMul(image_format.size().value().height(), image_format.bytes_per_row().value())
.ValueOrDie();
return true;
}
default:
return false;
}
}
if (plane == 2) {
switch (image_format.pixel_format().value()) {
case PixelFormat::kI420:
case PixelFormat::kYv12: {
auto luma_bytes =
CheckMul(image_format.size().value().height(), image_format.bytes_per_row().value());
auto one_chroma_plane_bytes = CheckMul(CheckDiv(image_format.size().value().height(), 2),
CheckDiv(image_format.bytes_per_row().value(), 2));
auto offset_just_past_luma_and_one_chroma = CheckAdd(luma_bytes, one_chroma_plane_bytes);
// 2nd chroma plane is just past luma and 1st chroma plane
*offset_out = offset_just_past_luma_and_one_chroma.ValueOrDie();
return true;
}
default:
return false;
}
}
return false;
}
bool ImageFormatPlaneRowBytes(const ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) const override {
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
if (plane == 0) {
*row_bytes_out = image_format.bytes_per_row().value();
return true;
}
if (plane == 1) {
switch (pixel_format_and_modifier.pixel_format) {
case PixelFormat::kNv12:
case PixelFormat::kP010:
*row_bytes_out = image_format.bytes_per_row().value();
return true;
case PixelFormat::kI420:
case PixelFormat::kYv12:
*row_bytes_out = image_format.bytes_per_row().value() / 2;
return true;
default:
return false;
}
} else if (plane == 2) {
switch (pixel_format_and_modifier.pixel_format) {
case PixelFormat::kI420:
case PixelFormat::kYv12:
*row_bytes_out = image_format.bytes_per_row().value() / 2;
return true;
default:
return false;
}
}
return false;
}
bool ImageFormatMinimumRowBytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) const override {
return linear_minimum_row_bytes(constraints, width, minimum_row_bytes_out);
}
};
constexpr LinearFormats kLinearFormats;
class GoldfishFormats : public ImageFormatSet {
public:
const char* Name() const override { return "GoldfishFormats"; }
bool IsSupported(const PixelFormatAndModifier& pixel_format) const override {
switch (pixel_format.pixel_format_modifier) {
case fuchsia_images2::PixelFormatModifier::kGoogleGoldfishOptimal:
return true;
default:
return false;
}
}
uint64_t ImageFormatImageSize(const ImageFormat& image_format) const override {
ZX_DEBUG_ASSERT(image_format.pixel_format().has_value());
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
ZX_DEBUG_ASSERT(IsSupported(pixel_format_and_modifier));
ZX_DEBUG_ASSERT(image_format.size().has_value());
ZX_DEBUG_ASSERT(image_format.bytes_per_row().has_value());
uint32_t surface_height = image_format.size()->height();
uint32_t bytes_per_row = image_format.bytes_per_row().value();
return linear_size(surface_height, bytes_per_row, image_format.pixel_format().value());
}
bool ImageFormatPlaneByteOffset(const ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) const override {
ZX_DEBUG_ASSERT(IsSupported(PixelFormatAndModifier(
image_format.pixel_format().value(), image_format.pixel_format_modifier().value())));
if (plane == 0) {
*offset_out = 0;
return true;
}
return false;
}
bool ImageFormatPlaneRowBytes(const ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) const override {
if (plane == 0) {
*row_bytes_out = image_format.bytes_per_row().value();
return true;
}
return false;
}
bool ImageFormatMinimumRowBytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) const override {
return false;
}
};
class ArmTELinearFormats : public ImageFormatSet {
public:
const char* Name() const override { return "ArmTELinearFormats"; }
bool IsSupported(const PixelFormatAndModifier& pixel_format) const override {
if (pixel_format.pixel_format_modifier != fuchsia_images2::PixelFormatModifier::kArmLinearTe)
return false;
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
return false;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
case PixelFormat::kI420:
case PixelFormat::kM420:
case PixelFormat::kNv12:
case PixelFormat::kYuy2:
case PixelFormat::kYv12:
case PixelFormat::kR5G6B5:
case PixelFormat::kR3G3B2:
case PixelFormat::kR2G2B2X2:
case PixelFormat::kL8:
case PixelFormat::kR8:
case PixelFormat::kR8G8:
case PixelFormat::kA2B10G10R10:
case PixelFormat::kA2R10G10B10:
return true;
default:
return false;
}
return false;
}
uint64_t ImageFormatImageSize(const fuchsia_images2::ImageFormat& image_format) const override {
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
ZX_DEBUG_ASSERT(IsSupported(pixel_format_and_modifier));
ZX_DEBUG_ASSERT(image_format.size().has_value());
ZX_DEBUG_ASSERT(image_format.bytes_per_row().has_value());
uint32_t bytes_per_row = image_format.bytes_per_row().value();
uint64_t size = linear_size(image_format.size()->height(), bytes_per_row,
pixel_format_and_modifier.pixel_format);
uint64_t crc_size = arm_transaction_elimination_buffer_size(size, image_format.size()->width(),
image_format.size()->height());
return size + crc_size;
}
bool ImageFormatPlaneByteOffset(const fuchsia_images2::ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) const override {
if (plane < kTransactionEliminationPlane) {
return kLinearFormats.ImageFormatPlaneByteOffset(image_format, plane, offset_out);
}
if (plane == kTransactionEliminationPlane) {
ZX_DEBUG_ASSERT(image_format.size().has_value());
ZX_DEBUG_ASSERT(image_format.bytes_per_row().has_value());
uint32_t bytes_per_row = image_format.bytes_per_row().value();
uint64_t size = linear_size(image_format.size()->height(), bytes_per_row,
image_format.pixel_format().value());
*offset_out = fbl::round_up(size, 64u);
return true;
}
return false;
}
bool ImageFormatPlaneRowBytes(const fuchsia_images2::ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) const override {
if (plane < kTransactionEliminationPlane) {
return kLinearFormats.ImageFormatPlaneRowBytes(image_format, plane, row_bytes_out);
}
if (plane == kTransactionEliminationPlane) {
if (!image_format.size().has_value()) {
return false;
}
*row_bytes_out = arm_transaction_elimination_row_size(image_format.size()->width());
return true;
}
return false;
}
bool ImageFormatMinimumRowBytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) const override {
return linear_minimum_row_bytes(constraints, width, minimum_row_bytes_out);
}
};
constexpr IntelTiledFormats kIntelFormats;
constexpr AfbcFormats kAfbcFormats;
constexpr ArmTELinearFormats kArmTELinearFormats;
constexpr GoldfishFormats kGoldfishFormats;
constexpr const ImageFormatSet* kImageFormats[] = {
&kLinearFormats, &kIntelFormats, &kAfbcFormats, &kArmTELinearFormats, &kGoldfishFormats,
};
} // namespace
#if __Fuchsia_API_level__ >= 19
bool ImageFormatIsPixelFormatEqual(const PixelFormatAndModifier& a,
const PixelFormatAndModifier& b) {
if (a.pixel_format != b.pixel_format) {
return false;
}
if (a.pixel_format_modifier != b.pixel_format_modifier) {
return false;
}
return true;
}
bool ImageFormatIsPixelFormatEqual(const fuchsia_sysmem::wire::PixelFormat& wire_a_v1,
const fuchsia_sysmem::wire::PixelFormat& wire_b_v1) {
auto a_v1 = fidl::ToNatural(wire_a_v1);
auto b_v1 = fidl::ToNatural(wire_b_v1);
auto a_v2 = sysmem::V2CopyFromV1PixelFormat(a_v1);
auto b_v2 = sysmem::V2CopyFromV1PixelFormat(b_v1);
return ImageFormatIsPixelFormatEqual(a_v2, b_v2);
}
bool ImageFormatIsSupportedColorSpaceForPixelFormat(const fuchsia_images2::ColorSpace& color_space,
const PixelFormatAndModifier& pixel_format) {
if (color_space == ColorSpace::kPassthrough) {
return true;
}
// Ignore pixel format modifier - assume it has already been checked.
auto color_space_sampling_info_iter = kColorSpaceSamplingInfo.find(color_space);
if (color_space_sampling_info_iter == kColorSpaceSamplingInfo.end()) {
return false;
}
auto pixel_format_sampling_info_iter = kPixelFormatSamplingInfo.find(pixel_format.pixel_format);
if (pixel_format_sampling_info_iter == kPixelFormatSamplingInfo.end()) {
return false;
}
const SamplingInfo& color_space_sampling_info = color_space_sampling_info_iter->second;
const SamplingInfo& pixel_format_sampling_info = pixel_format_sampling_info_iter->second;
std::vector<ColorType> color_type_intersection;
std::set_intersection(
color_space_sampling_info.color_types.begin(), color_space_sampling_info.color_types.end(),
pixel_format_sampling_info.color_types.begin(), pixel_format_sampling_info.color_types.end(),
std::back_inserter(color_type_intersection));
if (color_type_intersection.empty()) {
return false;
}
bool is_bits_per_sample_match_found = false;
for (uint32_t bits_per_sample : color_space_sampling_info.possible_bits_per_sample) {
auto pixel_format_bits_per_sample_iter =
pixel_format_sampling_info.possible_bits_per_sample.find(bits_per_sample);
if (pixel_format_bits_per_sample_iter !=
pixel_format_sampling_info.possible_bits_per_sample.end()) {
is_bits_per_sample_match_found = true;
break;
}
}
return is_bits_per_sample_match_found;
}
bool ImageFormatIsSupportedColorSpaceForPixelFormat(
const fuchsia_sysmem::wire::ColorSpace& wire_color_space_v1,
const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto color_space_v1 = fidl::ToNatural(wire_color_space_v1);
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto color_space_v2 = sysmem::V2CopyFromV1ColorSpace(color_space_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatIsSupportedColorSpaceForPixelFormat(color_space_v2, pixel_format_v2);
}
#endif // __Fuchsia_API_level__ >= 19
bool ImageFormatIsSupported(const PixelFormatAndModifier& pixel_format) {
return std::any_of(std::begin(kImageFormats), std::end(kImageFormats),
[pixel_format](const ImageFormatSet* format_set) {
return format_set->IsSupported(pixel_format);
});
}
#if __Fuchsia_API_level__ >= 19
bool ImageFormatIsSupported(const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatIsSupported(pixel_format_v2);
}
uint32_t ImageFormatBitsPerPixel(const PixelFormatAndModifier& pixel_format) {
ZX_DEBUG_ASSERT(ImageFormatIsSupported(pixel_format));
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
// impossible; checked previously.
ZX_DEBUG_ASSERT(false);
return 0u;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
return 4u * 8u;
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
return 4u * 8u;
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
return 3u * 8u;
case PixelFormat::kI420:
return 12u;
case PixelFormat::kM420:
return 12u;
case PixelFormat::kNv12:
return 12u;
case PixelFormat::kP010:
return 15u;
case PixelFormat::kYuy2:
return 2u * 8u;
case PixelFormat::kYv12:
return 12u;
case PixelFormat::kR5G6B5:
return 16u;
case PixelFormat::kR3G3B2:
case PixelFormat::kR2G2B2X2:
case PixelFormat::kL8:
case PixelFormat::kR8:
return 8u;
case PixelFormat::kR8G8:
return 16u;
case PixelFormat::kA2B10G10R10:
case PixelFormat::kA2R10G10B10:
return 2u + 3 * 10u;
default:
ZX_PANIC("Unknown Pixel Format: %u", sysmem::fidl_underlying_cast(pixel_format.pixel_format));
}
}
uint32_t ImageFormatBitsPerPixel(const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatBitsPerPixel(pixel_format_v2);
}
#endif // __Fuchsia_API_level__ >= 19
uint32_t ImageFormatStrideBytesPerWidthPixel(const PixelFormatAndModifier& pixel_format) {
ZX_DEBUG_ASSERT(ImageFormatIsSupported(pixel_format));
// This list should match the one in garnet/public/rust/fuchsia-framebuffer/src/sysmem.rs.
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
// impossible; checked previously.
ZX_DEBUG_ASSERT(false);
return 0u;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
return 4u;
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
return 4u;
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
return 3u;
case PixelFormat::kI420:
return 1u;
case PixelFormat::kM420:
return 1u;
case PixelFormat::kNv12:
return 1u;
case PixelFormat::kP010:
return 2u;
case PixelFormat::kYuy2:
return 2u;
case PixelFormat::kYv12:
return 1u;
case PixelFormat::kR5G6B5:
return 2u;
case PixelFormat::kR3G3B2:
return 1u;
case PixelFormat::kR2G2B2X2:
return 1u;
case PixelFormat::kL8:
return 1u;
case PixelFormat::kR8:
return 1u;
case PixelFormat::kR8G8:
return 2u;
case PixelFormat::kA2B10G10R10:
return 4u;
case PixelFormat::kA2R10G10B10:
return 4u;
default:
ZX_PANIC("Unknown Pixel Format: %u", sysmem::fidl_underlying_cast(pixel_format.pixel_format));
}
}
#if __Fuchsia_API_level__ >= 19
uint32_t ImageFormatStrideBytesPerWidthPixel(
const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatStrideBytesPerWidthPixel(pixel_format_v2);
}
uint64_t ImageFormatImageSize(const fuchsia_images2::ImageFormat& image_format) {
ZX_DEBUG_ASSERT(image_format.pixel_format().has_value());
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
for (auto format_set : kImageFormats) {
if (format_set->IsSupported(pixel_format_and_modifier)) {
return format_set->ImageFormatImageSize(image_format);
}
}
ZX_PANIC("Unknown Pixel Format: %u",
sysmem::fidl_underlying_cast(image_format.pixel_format().value()));
return 0;
}
uint64_t ImageFormatImageSize(const fuchsia_images2::wire::ImageFormat& image_format) {
return ImageFormatImageSize(fidl::ToNatural(image_format));
}
uint64_t ImageFormatImageSize(const fuchsia_sysmem::wire::ImageFormat2& wire_image_format_v1) {
auto image_format_v1 = fidl::ToNatural(wire_image_format_v1);
auto image_format_v2 = sysmem::V2CopyFromV1ImageFormat(image_format_v1).take_value();
return ImageFormatImageSize(image_format_v2);
}
uint32_t ImageFormatSurfaceWidthMinDivisor(const PixelFormatAndModifier& pixel_format) {
ZX_DEBUG_ASSERT(ImageFormatIsSupported(pixel_format));
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
// impossible; checked previously.
ZX_DEBUG_ASSERT(false);
return 0u;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
return 1u;
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
return 1u;
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
return 1u;
case PixelFormat::kI420:
return 2u;
case PixelFormat::kM420:
return 2u;
case PixelFormat::kNv12:
return 2u;
case PixelFormat::kP010:
return 2u;
case PixelFormat::kYuy2:
return 2u;
case PixelFormat::kYv12:
return 2u;
case PixelFormat::kR5G6B5:
return 1u;
case PixelFormat::kR3G3B2:
return 1u;
case PixelFormat::kR2G2B2X2:
return 1u;
case PixelFormat::kL8:
return 1u;
case PixelFormat::kR8:
return 1u;
case PixelFormat::kR8G8:
return 1u;
case PixelFormat::kA2B10G10R10:
return 1u;
case PixelFormat::kA2R10G10B10:
return 1u;
default:
ZX_PANIC("Unknown Pixel Format: %u", sysmem::fidl_underlying_cast(pixel_format.pixel_format));
}
}
uint32_t ImageFormatCodedWidthMinDivisor(
const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatSurfaceWidthMinDivisor(pixel_format_v2);
}
uint32_t ImageFormatSurfaceHeightMinDivisor(const PixelFormatAndModifier& pixel_format) {
ZX_DEBUG_ASSERT(ImageFormatIsSupported(pixel_format));
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
// impossible; checked previously.
ZX_DEBUG_ASSERT(false);
return 0u;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
return 1u;
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
return 1u;
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
return 1u;
case PixelFormat::kI420:
return 2u;
case PixelFormat::kM420:
return 2u;
case PixelFormat::kNv12:
return 2u;
case PixelFormat::kP010:
return 2u;
case PixelFormat::kYuy2:
return 2u;
case PixelFormat::kYv12:
return 2u;
case PixelFormat::kR5G6B5:
return 1u;
case PixelFormat::kR3G3B2:
return 1u;
case PixelFormat::kR2G2B2X2:
return 1u;
case PixelFormat::kL8:
return 1u;
case PixelFormat::kR8:
return 1u;
case PixelFormat::kR8G8:
return 1u;
case PixelFormat::kA2B10G10R10:
return 1u;
case PixelFormat::kA2R10G10B10:
return 1u;
default:
ZX_PANIC("Unknown Pixel Format: %u", sysmem::fidl_underlying_cast(pixel_format.pixel_format));
}
}
uint32_t ImageFormatCodedHeightMinDivisor(
const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatSurfaceHeightMinDivisor(pixel_format_v2);
}
uint32_t ImageFormatSampleAlignment(const PixelFormatAndModifier& pixel_format) {
ZX_DEBUG_ASSERT(ImageFormatIsSupported(pixel_format));
switch (pixel_format.pixel_format) {
case PixelFormat::kInvalid:
case PixelFormat::kDoNotCare:
case PixelFormat::kMjpeg:
// impossible; checked previously.
ZX_DEBUG_ASSERT(false);
return 0u;
case PixelFormat::kR8G8B8A8:
case PixelFormat::kR8G8B8X8:
return 4u;
case PixelFormat::kB8G8R8A8:
case PixelFormat::kB8G8R8X8:
return 4u;
case PixelFormat::kB8G8R8:
case PixelFormat::kR8G8B8:
return 1u;
case PixelFormat::kI420:
return 2u;
case PixelFormat::kM420:
return 2u;
case PixelFormat::kNv12:
return 2u;
case PixelFormat::kP010:
return 4u;
case PixelFormat::kYuy2:
return 2u;
case PixelFormat::kYv12:
return 2u;
case PixelFormat::kR5G6B5:
return 2u;
case PixelFormat::kR3G3B2:
return 1u;
case PixelFormat::kR2G2B2X2:
return 1u;
case PixelFormat::kL8:
return 1u;
case PixelFormat::kR8:
return 1u;
case PixelFormat::kR8G8:
return 2u;
case PixelFormat::kA2B10G10R10:
return 4u;
case PixelFormat::kA2R10G10B10:
return 4u;
default:
ZX_PANIC("Unknown Pixel Format: %u", sysmem::fidl_underlying_cast(pixel_format.pixel_format));
}
}
uint32_t ImageFormatSampleAlignment(const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatSampleAlignment(pixel_format_v2);
}
#endif // __Fuchsia_API_level__ >= 19
bool ImageFormatMinimumRowBytes(const fuchsia_sysmem2::ImageFormatConstraints& constraints,
uint32_t width, uint32_t* minimum_row_bytes_out) {
ZX_ASSERT(width != 0);
ZX_DEBUG_ASSERT(minimum_row_bytes_out);
// Caller must set pixel_format.
ZX_DEBUG_ASSERT(constraints.pixel_format().has_value());
auto pixel_format_and_modifier = PixelFormatAndModifierFromConstraints(constraints);
for (auto& format_set : kImageFormats) {
if (format_set->IsSupported(pixel_format_and_modifier)) {
bool result =
format_set->ImageFormatMinimumRowBytes(constraints, width, minimum_row_bytes_out);
ZX_ASSERT(!result || *minimum_row_bytes_out != 0);
return result;
}
}
return false;
}
#if __Fuchsia_API_level__ >= 19
bool ImageFormatMinimumRowBytes(
const fuchsia_sysmem2::wire::ImageFormatConstraints& wire_constraints, uint32_t width,
uint32_t* minimum_row_bytes_out) {
return ImageFormatMinimumRowBytes(fidl::ToNatural(wire_constraints), width,
minimum_row_bytes_out);
}
bool ImageFormatMinimumRowBytes(
const fuchsia_sysmem::wire::ImageFormatConstraints& wire_image_format_constraints_v1,
uint32_t width, uint32_t* minimum_row_bytes_out) {
auto image_format_constraints_v1 = fidl::ToNatural(wire_image_format_constraints_v1);
auto image_format_constraints_v2 =
sysmem::V2CopyFromV1ImageFormatConstraints(image_format_constraints_v1).take_value();
return ImageFormatMinimumRowBytes(image_format_constraints_v2, width, minimum_row_bytes_out);
}
fpromise::result<fuchsia_images2::wire::PixelFormat> ImageFormatConvertZbiToSysmemPixelFormat_v2(
zbi_pixel_format_t zx_pixel_format) {
switch (zx_pixel_format) {
case ZBI_PIXEL_FORMAT_RGB_565:
return fpromise::ok(PixelFormat::kR5G6B5);
case ZBI_PIXEL_FORMAT_RGB_332:
return fpromise::ok(PixelFormat::kR3G3B2);
case ZBI_PIXEL_FORMAT_RGB_2220:
return fpromise::ok(PixelFormat::kR2G2B2X2);
case ZBI_PIXEL_FORMAT_ARGB_8888:
// Switching to using alpha.
case ZBI_PIXEL_FORMAT_RGB_X888:
// TODO(b/329142833): Change this to B8G8R8X8 when relevant participants support R8G8B8X8. The
// main benefit is ensuring that consumer participants reliably ignore the X/A byte, treating
// the RGB portion as fully opaque regardless of the contents of the X/A byte. Currently this
// happens to work out ok, but really we should ensure that participants agree re. X vs. A.
return fpromise::ok(PixelFormat::kB8G8R8A8);
case ZBI_PIXEL_FORMAT_MONO_8:
return fpromise::ok(PixelFormat::kL8);
case ZBI_PIXEL_FORMAT_I420:
return fpromise::ok(PixelFormat::kI420);
case ZBI_PIXEL_FORMAT_NV12:
return fpromise::ok(PixelFormat::kNv12);
case ZBI_PIXEL_FORMAT_RGB_888:
return fpromise::ok(PixelFormat::kB8G8R8);
case ZBI_PIXEL_FORMAT_ABGR_8888:
// Switching to using alpha.
case ZBI_PIXEL_FORMAT_BGR_888_X:
// TODO(b/329142833): Change this to R8G8B8X8 when relevant participants support R8G8B8X8. The
// main benefit is ensuring that consumer participants reliably ignore the X/A byte, treating
// the RGB portion as fully opaque regardless of the contents of the X/A byte. Currently this
// happens to work out ok, but really we should ensure that participants agree re. X vs. A.
return fpromise::ok(PixelFormat::kR8G8B8A8);
case ZBI_PIXEL_FORMAT_ARGB_2_10_10_10:
return fpromise::ok(PixelFormat::kA2R10G10B10);
case ZBI_PIXEL_FORMAT_ABGR_2_10_10_10:
return fpromise::ok(PixelFormat::kA2B10G10R10);
default:
return fpromise::error();
}
}
#endif // __Fuchsia_API_level__ >= 19
fpromise::result<ImageFormat> ImageConstraintsToFormat(const ImageFormatConstraints& constraints,
uint32_t width, uint32_t height) {
if ((constraints.min_size().has_value() && height < constraints.min_size()->height()) ||
(constraints.max_size().has_value() && height > constraints.max_size()->height())) {
return fpromise::error();
}
if ((constraints.min_size().has_value() && width < constraints.min_size()->width()) ||
(constraints.max_size().has_value() && width > constraints.max_size()->width())) {
return fpromise::error();
}
ImageFormat result;
uint32_t minimum_row_bytes;
if (!ImageFormatMinimumRowBytes(constraints, width, &minimum_row_bytes)) {
return fpromise::error();
}
ZX_ASSERT(minimum_row_bytes != 0);
result.bytes_per_row() = minimum_row_bytes;
result.pixel_format() = constraints.pixel_format().value();
result.pixel_format_modifier() = constraints.pixel_format_modifier().value();
result.size() = {width, height};
// We intentionally default to x, y == 0, 0 here, since that's by far the most common. The caller
// can fix this up if the caller needs non-zero x or non-zero y.
result.display_rect() = {0, 0, width, height};
if (constraints.color_spaces().has_value() && !constraints.color_spaces()->empty()) {
result.color_space() = constraints.color_spaces()->at(0);
}
// result's pixel_aspect_ratio field remains un-set which is equivalent to 1,1
return fpromise::ok(std::move(result));
}
#if __Fuchsia_API_level__ >= 19
fpromise::result<ImageFormatWire> ImageConstraintsToFormat(
fidl::AnyArena& allocator, const ImageFormatConstraintsWire& wire_constraints, uint32_t width,
uint32_t height) {
auto constraints = fidl::ToNatural(wire_constraints);
auto result = ImageConstraintsToFormat(constraints, width, height);
if (!result.is_ok()) {
return fpromise::error();
}
return fpromise::ok(fidl::ToWire(allocator, result.take_value()));
}
#endif // __Fuchsia_API_level__ >= 19
fpromise::result<fuchsia_sysmem::wire::ImageFormat2> ImageConstraintsToFormat(
const fuchsia_sysmem::wire::ImageFormatConstraints& wire_image_format_constraints_v1,
uint32_t width, uint32_t height) {
auto image_format_constraints_v1 = fidl::ToNatural(wire_image_format_constraints_v1);
auto image_format_constraints_v2_result =
sysmem::V2CopyFromV1ImageFormatConstraints(image_format_constraints_v1);
if (!image_format_constraints_v2_result.is_ok()) {
return fpromise::error();
}
auto image_format_constraints_v2 = image_format_constraints_v2_result.take_value();
auto v2_out_result = ImageConstraintsToFormat(image_format_constraints_v2, width, height);
if (!v2_out_result.is_ok()) {
return fpromise::error();
}
auto v2_out = v2_out_result.take_value();
auto v1_out_result = sysmem::V1CopyFromV2ImageFormat(v2_out);
if (!v1_out_result) {
return fpromise::error();
}
// This arena isn't relied upon by the returned value, because kMaxOutOfLine == 0.
fidl::Arena arena;
ZX_DEBUG_ASSERT(fidl::TypeTraits<fuchsia_sysmem::wire::ImageFormat2>::kMaxOutOfLine == 0);
auto wire_v1 = fidl::ToWire(arena, v1_out_result.take_value());
return fpromise::ok(wire_v1);
}
bool ImageFormatPlaneByteOffset(const ImageFormat& image_format, uint32_t plane,
uint64_t* offset_out) {
ZX_DEBUG_ASSERT(offset_out);
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
for (auto& format_set : kImageFormats) {
if (format_set->IsSupported(pixel_format_and_modifier)) {
return format_set->ImageFormatPlaneByteOffset(image_format, plane, offset_out);
}
}
return false;
}
#if __Fuchsia_API_level__ >= 19
bool ImageFormatPlaneByteOffset(const ImageFormatWire& image_format, uint32_t plane,
uint64_t* offset_out) {
return ImageFormatPlaneByteOffset(fidl::ToNatural(image_format), plane, offset_out);
}
#endif // __Fuchsia_API_level__ >= 19
bool ImageFormatPlaneByteOffset(const fuchsia_sysmem::wire::ImageFormat2& wire_image_format_v1,
uint32_t plane, uint64_t* offset_out) {
ZX_DEBUG_ASSERT(offset_out);
auto image_format_v1 = fidl::ToNatural(wire_image_format_v1);
auto image_format_v2_result = sysmem::V2CopyFromV1ImageFormat(image_format_v1);
if (!image_format_v2_result.is_ok()) {
return false;
}
auto image_format_v2 = image_format_v2_result.take_value();
return ImageFormatPlaneByteOffset(image_format_v2, plane, offset_out);
}
bool ImageFormatPlaneRowBytes(const ImageFormat& image_format, uint32_t plane,
uint32_t* row_bytes_out) {
ZX_DEBUG_ASSERT(row_bytes_out);
auto pixel_format_and_modifier = PixelFormatAndModifierFromImageFormat(image_format);
for (auto& format_set : kImageFormats) {
if (format_set->IsSupported(pixel_format_and_modifier)) {
return format_set->ImageFormatPlaneRowBytes(image_format, plane, row_bytes_out);
}
}
return false;
}
#if __Fuchsia_API_level__ >= 19
bool ImageFormatPlaneRowBytes(const ImageFormatWire& wire_image_format, uint32_t plane,
uint32_t* row_bytes_out) {
return ImageFormatPlaneRowBytes(fidl::ToNatural(wire_image_format), plane, row_bytes_out);
}
#endif // __Fuchsia_API_level__ >= 19
bool ImageFormatPlaneRowBytes(const fuchsia_sysmem::wire::ImageFormat2& wire_image_format_v1,
uint32_t plane, uint32_t* row_bytes_out) {
ZX_DEBUG_ASSERT(row_bytes_out);
auto image_format_v1 = fidl::ToNatural(wire_image_format_v1);
auto image_format_v2_result = sysmem::V2CopyFromV1ImageFormat(image_format_v1);
if (!image_format_v2_result.is_ok()) {
return false;
}
auto image_format_v2 = image_format_v2_result.take_value();
return ImageFormatPlaneRowBytes(image_format_v2, plane, row_bytes_out);
}
#if __Fuchsia_API_level__ >= 19
bool ImageFormatCompatibleWithProtectedMemory(const PixelFormatAndModifier& pixel_format) {
// AKA kFormatModifierLinear
if (pixel_format.pixel_format_modifier == fuchsia_images2::PixelFormatModifier::kLinear) {
return true;
}
constexpr uint64_t kArmLinearFormat = 0x0800000000000000ul;
switch (fidl::ToUnderlying(pixel_format.pixel_format_modifier) &
~AfbcFormats::kAfbcModifierMask) {
case kArmLinearFormat:
case fidl::ToUnderlying(fuchsia_images2::PixelFormatModifier::kArmAfbc16X16):
case fidl::ToUnderlying(fuchsia_images2::PixelFormatModifier::kArmAfbc32X8):
// TE formats occasionally need CPU writes to the TE buffer.
return !(fidl::ToUnderlying(pixel_format.pixel_format_modifier) &
fuchsia_images2::kFormatModifierArmTeBit);
default:
return true;
}
}
bool ImageFormatCompatibleWithProtectedMemory(
const fuchsia_sysmem::wire::PixelFormat& wire_pixel_format_v1) {
auto pixel_format_v1 = fidl::ToNatural(wire_pixel_format_v1);
auto pixel_format_v2 = sysmem::V2CopyFromV1PixelFormat(pixel_format_v1);
return ImageFormatCompatibleWithProtectedMemory(pixel_format_v2);
}
#endif // __Fuchsia_API_level__ >= 19