Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Impeller] make drawAtlas always use porterduff or vertices_uber shader #52348

Merged
merged 1 commit into from
Apr 24, 2024
Merged
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
405 changes: 59 additions & 346 deletions impeller/entity/contents/atlas_contents.cc
Original file line number Diff line number Diff line change
@@ -3,18 +3,14 @@
// found in the LICENSE file.

#include <optional>
#include <unordered_map>
#include <utility>

#include "impeller/core/formats.h"
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/blend_filter_contents.h"
#include "impeller/entity/contents/filters/color_filter_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/texture_fill.frag.h"
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/geometry/color.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/vertex_buffer_builder.h"
@@ -59,87 +55,6 @@ void AtlasContents::SetCullRect(std::optional<Rect> cull_rect) {
cull_rect_ = cull_rect;
}

struct AtlasBlenderKey {
Color color;
Rect rect;
uint32_t color_key;

struct Hash {
std::size_t operator()(const AtlasBlenderKey& key) const {
return fml::HashCombine(key.color_key, key.rect.GetWidth(),
key.rect.GetHeight(), key.rect.GetX(),
key.rect.GetY());
}
};

struct Equal {
bool operator()(const AtlasBlenderKey& lhs,
const AtlasBlenderKey& rhs) const {
return lhs.rect == rhs.rect && lhs.color_key == rhs.color_key;
}
};
};

std::shared_ptr<SubAtlasResult> AtlasContents::GenerateSubAtlas() const {
FML_DCHECK(colors_.size() > 0 && blend_mode_ != BlendMode::kSource &&
blend_mode_ != BlendMode::kDestination);

std::unordered_map<AtlasBlenderKey, std::vector<Matrix>,
AtlasBlenderKey::Hash, AtlasBlenderKey::Equal>
sub_atlas = {};

for (auto i = 0u; i < texture_coords_.size(); i++) {
AtlasBlenderKey key = {.color = colors_[i],
.rect = texture_coords_[i],
.color_key = Color::ToIColor(colors_[i])};
if (sub_atlas.find(key) == sub_atlas.end()) {
sub_atlas[key] = {transforms_[i]};
} else {
sub_atlas[key].push_back(transforms_[i]);
}
}

auto result = std::make_shared<SubAtlasResult>();
Scalar x_offset = 0.0;
Scalar y_offset = 0.0;
Scalar x_extent = 0.0;
Scalar y_extent = 0.0;

for (auto it = sub_atlas.begin(); it != sub_atlas.end(); it++) {
// This size was arbitrarily chosen to keep the textures from getting too
// wide. We could instead use a more generic rect packer but in the majority
// of cases the sample rects will be fairly close in size making this a good
// enough approximation.
if (x_offset >= 1000) {
y_offset = y_extent + 1;
x_offset = 0.0;
}

auto key = it->first;
auto transforms = it->second;

auto new_rect = Rect::MakeXYWH(x_offset, y_offset, key.rect.GetWidth(),
key.rect.GetHeight());
auto sub_transform = Matrix::MakeTranslation(Vector2(x_offset, y_offset));

x_offset += std::ceil(key.rect.GetWidth()) + 1.0;

result->sub_texture_coords.push_back(key.rect);
result->sub_colors.push_back(key.color);
result->sub_transforms.push_back(sub_transform);

x_extent = std::max(x_extent, x_offset);
y_extent = std::max(y_extent, std::ceil(y_offset + key.rect.GetHeight()));

for (auto transform : transforms) {
result->result_texture_coords.push_back(new_rect);
result->result_transforms.push_back(transform);
}
}
result->size = ISize(std::ceil(x_extent), std::ceil(y_extent));
return result;
}

std::optional<Rect> AtlasContents::GetCoverage(const Entity& entity) const {
if (cull_rect_.has_value()) {
return cull_rect_.value().TransformBounds(entity.GetTransform());
@@ -190,54 +105,52 @@ bool AtlasContents::Render(const ContentContext& renderer,
return true;
}

// Ensure that we use the actual computed bounds and not a cull-rect
// approximation of them.
auto coverage = ComputeBoundingBox();

if (blend_mode_ == BlendMode::kSource || colors_.size() == 0) {
auto child_contents = AtlasTextureContents(*this);
child_contents.SetAlpha(alpha_);
child_contents.SetCoverage(coverage);
return child_contents.Render(renderer, entity, pass);
}
if (blend_mode_ == BlendMode::kDestination) {
auto child_contents = AtlasColorContents(*this);
child_contents.SetAlpha(alpha_);
child_contents.SetCoverage(coverage);
return child_contents.Render(renderer, entity, pass);
BlendMode blend_mode = blend_mode_;
if (colors_.empty()) {
blend_mode = BlendMode::kSource;
}

constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};

if (blend_mode_ <= BlendMode::kModulate) {
// Simple Porter-Duff blends can be accomplished without a subpass.
using VS = PorterDuffBlendPipeline::VertexShader;
using FS = PorterDuffBlendPipeline::FragmentShader;

VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.Reserve(texture_coords_.size() * 6);
const auto texture_size = texture_->GetSize();
auto& host_buffer = renderer.GetTransientsBuffer();
using VS = PorterDuffBlendPipeline::VertexShader;

for (size_t i = 0; i < texture_coords_.size(); i++) {
auto sample_rect = texture_coords_[i];
auto matrix = transforms_[i];
auto points = sample_rect.GetPoints();
auto transformed_points =
Rect::MakeSize(sample_rect.GetSize()).GetTransformedPoints(matrix);
auto color = colors_[i].Premultiply();
for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.vertices = transformed_points[indices[j]];
data.texture_coords = points[indices[j]] / texture_size;
data.color = color;
vtx_builder.AppendVertex(data);
}
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.Reserve(texture_coords_.size() * 6);
const auto texture_size = texture_->GetSize();
auto& host_buffer = renderer.GetTransientsBuffer();
bool has_colors = !colors_.empty();
for (size_t i = 0; i < texture_coords_.size(); i++) {
auto sample_rect = texture_coords_[i];
auto matrix = transforms_[i];
auto points = sample_rect.GetPoints();
auto transformed_points =
Rect::MakeSize(sample_rect.GetSize()).GetTransformedPoints(matrix);
Color color =
has_colors ? colors_[i].Premultiply() : Color::BlackTransparent();
for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.vertices = transformed_points[indices[j]];
data.texture_coords = points[indices[j]] / texture_size;
data.color = color;
vtx_builder.AppendVertex(data);
}
}

auto dst_sampler_descriptor = sampler_descriptor_;
if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
}
const std::unique_ptr<const Sampler>& dst_sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);

if (blend_mode <= BlendMode::kModulate) {
using FS = PorterDuffBlendPipeline::FragmentShader;

#ifdef IMPELLER_DEBUG
pass.SetCommandLabel(
SPrintF("DrawAtlas Blend (%s)", BlendModeToString(blend_mode_)));
SPrintF("DrawAtlas Blend (%s)", BlendModeToString(blend_mode)));
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(vtx_builder.CreateVertexBuffer(host_buffer));
pass.SetPipeline(
@@ -246,29 +159,24 @@ bool AtlasContents::Render(const ContentContext& renderer,
FS::FragInfo frag_info;
VS::FrameInfo frame_info;

auto dst_sampler_descriptor = sampler_descriptor_;
if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
}
const std::unique_ptr<const Sampler>& dst_sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);
FS::BindTextureSamplerDst(pass, texture_, dst_sampler);
frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();

frag_info.output_alpha = alpha_;
frag_info.input_alpha = 1.0;

auto inverted_blend_mode =
InvertPorterDuffBlend(blend_mode_).value_or(BlendMode::kSource);
InvertPorterDuffBlend(blend_mode).value_or(BlendMode::kSource);
auto blend_coefficients =
kPorterDuffCoefficients[static_cast<int>(inverted_blend_mode)];
frag_info.src_coeff = blend_coefficients[0];
frag_info.src_coeff_dst_alpha = blend_coefficients[1];
frag_info.dst_coeff = blend_coefficients[2];
frag_info.dst_coeff_src_alpha = blend_coefficients[3];
frag_info.dst_coeff_src_color = blend_coefficients[4];
// These values are ignored on platforms that natively support decal.
frag_info.tmx = static_cast<int>(Entity::TileMode::kDecal);
frag_info.tmy = static_cast<int>(Entity::TileMode::kDecal);

FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));

@@ -280,229 +188,34 @@ bool AtlasContents::Render(const ContentContext& renderer,
return pass.Draw().ok();
}

// Advanced blends.

auto sub_atlas = GenerateSubAtlas();
auto sub_coverage = Rect::MakeSize(sub_atlas->size);

auto src_contents = std::make_shared<AtlasTextureContents>(*this);
src_contents->SetSubAtlas(sub_atlas);
src_contents->SetCoverage(sub_coverage);

auto dst_contents = std::make_shared<AtlasColorContents>(*this);
dst_contents->SetSubAtlas(sub_atlas);
dst_contents->SetCoverage(sub_coverage);

Entity untransformed_entity;
auto contents = ColorFilterContents::MakeBlend(
blend_mode_,
{FilterInput::Make(dst_contents), FilterInput::Make(src_contents)});
auto snapshot =
contents->RenderToSnapshot(renderer, // renderer
untransformed_entity, // entity
std::nullopt, // coverage_limit
std::nullopt, // sampler_descriptor
true, // msaa_enabled
/*mip_count=*/1,
"AtlasContents Snapshot"); // label
if (!snapshot.has_value()) {
return false;
}

auto child_contents = AtlasTextureContents(*this);
child_contents.SetAlpha(alpha_);
child_contents.SetCoverage(coverage);
child_contents.SetTexture(snapshot.value().texture);
child_contents.SetUseDestination(true);
child_contents.SetSubAtlas(sub_atlas);
return child_contents.Render(renderer, entity, pass);
}

// AtlasTextureContents
// ---------------------------------------------------------

AtlasTextureContents::AtlasTextureContents(const AtlasContents& parent)
: parent_(parent) {}

AtlasTextureContents::~AtlasTextureContents() {}

std::optional<Rect> AtlasTextureContents::GetCoverage(
const Entity& entity) const {
return coverage_.TransformBounds(entity.GetTransform());
}

void AtlasTextureContents::SetAlpha(Scalar alpha) {
alpha_ = alpha;
}

void AtlasTextureContents::SetCoverage(Rect coverage) {
coverage_ = coverage;
}

void AtlasTextureContents::SetUseDestination(bool value) {
use_destination_ = value;
}

void AtlasTextureContents::SetSubAtlas(
const std::shared_ptr<SubAtlasResult>& subatlas) {
subatlas_ = subatlas;
}

void AtlasTextureContents::SetTexture(std::shared_ptr<Texture> texture) {
texture_ = std::move(texture);
}

bool AtlasTextureContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = TextureFillVertexShader;
using FS = TextureFillFragmentShader;

auto texture = texture_ ? texture_ : parent_.GetTexture();
if (texture == nullptr) {
return true;
}

std::vector<Rect> texture_coords;
std::vector<Matrix> transforms;
if (subatlas_) {
texture_coords = use_destination_ ? subatlas_->result_texture_coords
: subatlas_->sub_texture_coords;
transforms = use_destination_ ? subatlas_->result_transforms
: subatlas_->sub_transforms;
} else {
texture_coords = parent_.GetTextureCoordinates();
transforms = parent_.GetTransforms();
}

const Size texture_size(texture->GetSize());
VertexBufferBuilder<VS::PerVertexData> vertex_builder;
vertex_builder.Reserve(texture_coords.size() * 6);
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
for (size_t i = 0; i < texture_coords.size(); i++) {
auto sample_rect = texture_coords[i];
auto matrix = transforms[i];
auto points = sample_rect.GetPoints();
auto transformed_points =
Rect::MakeSize(sample_rect.GetSize()).GetTransformedPoints(matrix);

for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.position = transformed_points[indices[j]];
data.texture_coords = points[indices[j]] / texture_size;
vertex_builder.AppendVertex(data);
}
}

if (!vertex_builder.HasVertices()) {
return true;
}
using VUS = VerticesUberShader::VertexShader;
using FS = VerticesUberShader::FragmentShader;

pass.SetCommandLabel("AtlasTexture");

auto& host_buffer = renderer.GetTransientsBuffer();

VS::FrameInfo frame_info;
frame_info.mvp = entity.GetShaderTransform(pass);
frame_info.texture_sampler_y_coord_scale = texture->GetYCoordScale();
#ifdef IMPELLER_DEBUG
pass.SetCommandLabel(
SPrintF("DrawAtlas Advanced Blend (%s)", BlendModeToString(blend_mode)));
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(vtx_builder.CreateVertexBuffer(host_buffer));

auto options = OptionsFromPassAndEntity(pass, entity);
pass.SetPipeline(renderer.GetTexturePipeline(options));
pass.SetVertexBuffer(vertex_builder.CreateVertexBuffer(host_buffer));
VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info));
pass.SetPipeline(renderer.GetDrawVerticesUberShader(OptionsFromPass(pass)));
FS::BindTextureSampler(pass, texture_, dst_sampler);

VUS::FrameInfo frame_info;
FS::FragInfo frag_info;
frag_info.alpha = alpha_;

FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));
FS::BindTextureSampler(pass, texture,
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
parent_.GetSamplerDescriptor()));
return pass.Draw().ok();
}

// AtlasColorContents
// ---------------------------------------------------------

AtlasColorContents::AtlasColorContents(const AtlasContents& parent)
: parent_(parent) {}

AtlasColorContents::~AtlasColorContents() {}

std::optional<Rect> AtlasColorContents::GetCoverage(
const Entity& entity) const {
return coverage_.TransformBounds(entity.GetTransform());
}

void AtlasColorContents::SetAlpha(Scalar alpha) {
alpha_ = alpha;
}

void AtlasColorContents::SetCoverage(Rect coverage) {
coverage_ = coverage;
}

void AtlasColorContents::SetSubAtlas(
const std::shared_ptr<SubAtlasResult>& subatlas) {
subatlas_ = subatlas;
}

bool AtlasColorContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = GeometryColorPipeline::VertexShader;
using FS = GeometryColorPipeline::FragmentShader;

std::vector<Rect> texture_coords;
std::vector<Matrix> transforms;
std::vector<Color> colors;
if (subatlas_) {
texture_coords = subatlas_->sub_texture_coords;
colors = subatlas_->sub_colors;
transforms = subatlas_->sub_transforms;
} else {
texture_coords = parent_.GetTextureCoordinates();
transforms = parent_.GetTransforms();
colors = parent_.GetColors();
}

VertexBufferBuilder<VS::PerVertexData> vertex_builder;
vertex_builder.Reserve(texture_coords.size() * 6);
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
for (size_t i = 0; i < texture_coords.size(); i++) {
auto sample_rect = texture_coords[i];
auto matrix = transforms[i];
auto transformed_points =
Rect::MakeSize(sample_rect.GetSize()).GetTransformedPoints(matrix);

for (size_t j = 0; j < 6; j++) {
VS::PerVertexData data;
data.position = transformed_points[indices[j]];
data.color = colors[i].Premultiply();
vertex_builder.AppendVertex(data);
}
}

if (!vertex_builder.HasVertices()) {
return true;
}

pass.SetCommandLabel("AtlasColors");

auto& host_buffer = renderer.GetTransientsBuffer();

VS::FrameInfo frame_info;
frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();
frame_info.mvp = entity.GetShaderTransform(pass);

FS::FragInfo frag_info;
frag_info.alpha = alpha_;
frag_info.blend_mode = static_cast<int>(blend_mode);

// These values are ignored on platforms that natively support decal.
frag_info.tmx = static_cast<int>(Entity::TileMode::kDecal);
frag_info.tmy = static_cast<int>(Entity::TileMode::kDecal);

auto opts = OptionsFromPassAndEntity(pass, entity);
opts.blend_mode = BlendMode::kSourceOver;
pass.SetPipeline(renderer.GetGeometryColorPipeline(opts));
pass.SetVertexBuffer(vertex_builder.CreateVertexBuffer(host_buffer));
VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info));
FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));
VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info));

return pass.Draw().ok();
}

88 changes: 0 additions & 88 deletions impeller/entity/contents/atlas_contents.h
Original file line number Diff line number Diff line change
@@ -9,27 +9,12 @@
#include <memory>
#include <vector>

#include "flutter/fml/macros.h"
#include "impeller/core/sampler_descriptor.h"
#include "impeller/entity/contents/contents.h"
#include "impeller/entity/entity.h"

namespace impeller {

struct SubAtlasResult {
// Sub atlas values.
std::vector<Rect> sub_texture_coords;
std::vector<Color> sub_colors;
std::vector<Matrix> sub_transforms;

// Result atlas values.
std::vector<Rect> result_texture_coords;
std::vector<Matrix> result_transforms;

// Size of the sub-atlass.
ISize size;
};

class AtlasContents final : public Contents {
public:
explicit AtlasContents();
@@ -62,11 +47,6 @@ class AtlasContents final : public Contents {

const std::vector<Color>& GetColors() const;

/// @brief Compress a drawAtlas call with blending into a smaller sized atlas.
/// This atlas has no overlapping to ensure
/// blending behaves as if it were done in the fragment shader.
std::shared_ptr<SubAtlasResult> GenerateSubAtlas() const;

// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;

@@ -93,74 +73,6 @@ class AtlasContents final : public Contents {
AtlasContents& operator=(const AtlasContents&) = delete;
};

class AtlasTextureContents final : public Contents {
public:
explicit AtlasTextureContents(const AtlasContents& parent);

~AtlasTextureContents() override;

// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;

// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;

void SetAlpha(Scalar alpha);

void SetCoverage(Rect coverage);

void SetTexture(std::shared_ptr<Texture> texture);

void SetUseDestination(bool value);

void SetSubAtlas(const std::shared_ptr<SubAtlasResult>& subatlas);

private:
const AtlasContents& parent_;
Scalar alpha_ = 1.0;
Rect coverage_;
std::shared_ptr<Texture> texture_;
bool use_destination_ = false;
std::shared_ptr<SubAtlasResult> subatlas_;

AtlasTextureContents(const AtlasTextureContents&) = delete;

AtlasTextureContents& operator=(const AtlasTextureContents&) = delete;
};

class AtlasColorContents final : public Contents {
public:
explicit AtlasColorContents(const AtlasContents& parent);

~AtlasColorContents() override;

// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;

// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;

void SetAlpha(Scalar alpha);

void SetCoverage(Rect coverage);

void SetSubAtlas(const std::shared_ptr<SubAtlasResult>& subatlas);

private:
const AtlasContents& parent_;
Scalar alpha_ = 1.0;
Rect coverage_;
std::shared_ptr<SubAtlasResult> subatlas_;

AtlasColorContents(const AtlasColorContents&) = delete;

AtlasColorContents& operator=(const AtlasColorContents&) = delete;
};

} // namespace impeller

#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_ATLAS_CONTENTS_H_
63 changes: 0 additions & 63 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
@@ -2027,69 +2027,6 @@ TEST_P(EntityTest, SrgbToLinearFilter) {
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(EntityTest, AtlasContentsSubAtlas) {
auto boston = CreateTextureForFixture("boston.jpg");

{
auto contents = std::make_shared<AtlasContents>();
contents->SetBlendMode(BlendMode::kSourceOver);
contents->SetTexture(boston);
contents->SetColors({
Color::Red(),
Color::Red(),
Color::Red(),
});
contents->SetTextureCoordinates({
Rect::MakeLTRB(0, 0, 10, 10),
Rect::MakeLTRB(0, 0, 10, 10),
Rect::MakeLTRB(0, 0, 10, 10),
});
contents->SetTransforms({
Matrix::MakeTranslation(Vector2(0, 0)),
Matrix::MakeTranslation(Vector2(100, 100)),
Matrix::MakeTranslation(Vector2(200, 200)),
});

// Since all colors and sample rects are the same, there should
// only be a single entry in the sub atlas.
auto subatlas = contents->GenerateSubAtlas();
ASSERT_EQ(subatlas->sub_texture_coords.size(), 1u);
}

{
auto contents = std::make_shared<AtlasContents>();
contents->SetBlendMode(BlendMode::kSourceOver);
contents->SetTexture(boston);
contents->SetColors({
Color::Red(),
Color::Green(),
Color::Blue(),
});
contents->SetTextureCoordinates({
Rect::MakeLTRB(0, 0, 10, 10),
Rect::MakeLTRB(0, 0, 10, 10),
Rect::MakeLTRB(0, 0, 10, 10),
});
contents->SetTransforms({
Matrix::MakeTranslation(Vector2(0, 0)),
Matrix::MakeTranslation(Vector2(100, 100)),
Matrix::MakeTranslation(Vector2(200, 200)),
});

// Since all colors are different, there are three entires.
auto subatlas = contents->GenerateSubAtlas();
ASSERT_EQ(subatlas->sub_texture_coords.size(), 3u);

// The translations are kept but the sample rects point into
// different parts of the sub atlas.
ASSERT_EQ(subatlas->result_texture_coords[0], Rect::MakeXYWH(0, 0, 10, 10));
ASSERT_EQ(subatlas->result_texture_coords[1],
Rect::MakeXYWH(11, 0, 10, 10));
ASSERT_EQ(subatlas->result_texture_coords[2],
Rect::MakeXYWH(22, 0, 10, 10));
}
}

static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) {
Vector3 yuv;
switch (yuv_color_space) {