nvnflinger/gpu: implement blending

This commit is contained in:
Liam
2024-01-22 12:40:50 -05:00
parent 06fd7f2012
commit 962c82540c
37 changed files with 383 additions and 141 deletions

View File

@ -130,9 +130,9 @@ enum class AppletProgramId : u64 {
enum class LibraryAppletMode : u32 {
AllForeground = 0,
Background = 1,
NoUI = 2,
BackgroundIndirectDisplay = 3,
PartialForeground = 1,
NoUi = 2,
PartialForegroundIndirectDisplay = 3,
AllForegroundInitiallyHidden = 4,
};

View File

@ -68,9 +68,9 @@ void SoftwareKeyboard::Initialize() {
case LibraryAppletMode::AllForeground:
InitializeForeground();
break;
case LibraryAppletMode::Background:
case LibraryAppletMode::BackgroundIndirectDisplay:
InitializeBackground(applet_mode);
case LibraryAppletMode::PartialForeground:
case LibraryAppletMode::PartialForegroundIndirectDisplay:
InitializePartialForeground(applet_mode);
break;
default:
ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode);
@ -243,7 +243,7 @@ void SoftwareKeyboard::InitializeForeground() {
InitializeFrontendNormalKeyboard();
}
void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) {
void SoftwareKeyboard::InitializePartialForeground(LibraryAppletMode library_applet_mode) {
LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet.");
is_background = true;
@ -258,9 +258,9 @@ void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mod
swkbd_inline_initialize_arg.size());
if (swkbd_initialize_arg.library_applet_mode_flag) {
ASSERT(library_applet_mode == LibraryAppletMode::Background);
ASSERT(library_applet_mode == LibraryAppletMode::PartialForeground);
} else {
ASSERT(library_applet_mode == LibraryAppletMode::BackgroundIndirectDisplay);
ASSERT(library_applet_mode == LibraryAppletMode::PartialForegroundIndirectDisplay);
}
}

View File

@ -62,7 +62,7 @@ private:
void InitializeForeground();
/// Initializes the inline software keyboard.
void InitializeBackground(LibraryAppletMode library_applet_mode);
void InitializePartialForeground(LibraryAppletMode library_applet_mode);
/// Processes the text check sent by the application.
void ProcessTextCheck();

View File

@ -87,7 +87,7 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
// Set focus state
switch (mode) {
case LibraryAppletMode::AllForeground:
case LibraryAppletMode::NoUI:
case LibraryAppletMode::NoUi:
applet->focus_state = FocusState::InFocus;
applet->hid_registration.EnableAppletToGetInput(true);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
@ -99,8 +99,8 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
applet->hid_registration.EnableAppletToGetInput(false);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
break;
case LibraryAppletMode::Background:
case LibraryAppletMode::BackgroundIndirectDisplay:
case LibraryAppletMode::PartialForeground:
case LibraryAppletMode::PartialForegroundIndirectDisplay:
default:
applet->focus_state = FocusState::Background;
applet->hid_registration.EnableAppletToGetInput(true);

View File

@ -288,7 +288,8 @@ void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
}
Result ISelfController::EnsureBufferSharingEnabled(Kernel::KProcess* process) {
if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id)) {
if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id,
applet->library_applet_mode)) {
return ResultSuccess;
}

View File

@ -17,11 +17,12 @@ SystemBufferManager::~SystemBufferManager() {
// Clean up shared layers.
if (m_buffer_sharing_enabled) {
m_nvnflinger->GetSystemBufferManager().Finalize(m_process);
}
}
bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process,
AppletId applet_id) {
AppletId applet_id, LibraryAppletMode mode) {
if (m_nvnflinger) {
return m_buffer_sharing_enabled;
}
@ -36,9 +37,14 @@ bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel:
return false;
}
Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
if (mode == LibraryAppletMode::PartialForeground) {
blending = Nvnflinger::LayerBlending::Coverage;
}
const auto display_id = m_nvnflinger->OpenDisplay("Default").value();
const auto res = m_nvnflinger->GetSystemBufferManager().Initialize(
&m_system_shared_buffer_id, &m_system_shared_layer_id, display_id);
m_process, &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id, blending);
if (res.IsSuccess()) {
m_buffer_sharing_enabled = true;

View File

@ -27,7 +27,8 @@ public:
SystemBufferManager();
~SystemBufferManager();
bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id);
bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id,
LibraryAppletMode mode);
void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
u64* out_system_shared_layer_id) {

View File

@ -15,6 +15,22 @@
namespace Service::Nvidia::Devices {
namespace {
Tegra::BlendMode ConvertBlending(Service::Nvnflinger::LayerBlending blending) {
switch (blending) {
case Service::Nvnflinger::LayerBlending::None:
default:
return Tegra::BlendMode::Opaque;
case Service::Nvnflinger::LayerBlending::Premultiplied:
return Tegra::BlendMode::Premultiplied;
case Service::Nvnflinger::LayerBlending::Coverage:
return Tegra::BlendMode::Coverage;
}
}
} // namespace
nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
: nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
nvdisp_disp0::~nvdisp_disp0() = default;
@ -56,6 +72,7 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
.pixel_format = layer.format,
.transform_flags = layer.transform,
.crop_rect = layer.crop_rect,
.blending = ConvertBlending(layer.blending),
});
for (size_t i = 0; i < layer.acquire_fence.num_fences; i++) {

View File

@ -14,24 +14,19 @@
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
#include "core/hle/service/vi/layer/vi_layer.h"
#include "core/hle/service/vi/vi_results.h"
#include "video_core/gpu.h"
namespace Service::Nvnflinger {
namespace {
Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
std::unique_ptr<Kernel::KPageGroup>* out_page_group,
Core::System& system, u32 size) {
Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_group,
Core::System& system, u32 size) {
using Core::Memory::YUZU_PAGESIZE;
// Allocate memory for the system shared buffer.
// FIXME: Because the gmmu can only point to cpu addresses, we need
// to map this in the application space to allow it to be used.
// FIXME: Add proper smmu emulation.
// FIXME: This memory belongs to vi's .data section.
auto& kernel = system.Kernel();
auto* process = system.ApplicationProcess();
auto& page_table = process->GetPageTable();
// Hold a temporary page group reference while we try to map it.
auto pg = std::make_unique<Kernel::KPageGroup>(
@ -43,6 +38,30 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
Kernel::KMemoryManager::Direction::FromBack)));
// Fill the output data with red.
for (auto& block : *pg) {
u32* start = system.DeviceMemory().GetPointer<u32>(block.GetAddress());
u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
for (; start < end; start++) {
*start = 0xFF0000FF;
}
}
// Return the mapped page group.
*out_page_group = std::move(pg);
// We succeeded.
R_SUCCEED();
}
Result MapSharedBufferIntoProcessAddressSpace(Common::ProcessAddress* out_map_address,
std::unique_ptr<Kernel::KPageGroup>& pg,
Kernel::KProcess* process, Core::System& system) {
using Core::Memory::YUZU_PAGESIZE;
auto& page_table = process->GetPageTable();
// Get bounds of where mapping is possible.
const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
@ -64,9 +83,6 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
// Return failure, if necessary
R_UNLESS(i < 64, res);
// Return the mapped page group.
*out_page_group = std::move(pg);
// We succeeded.
R_SUCCEED();
}
@ -135,6 +151,13 @@ Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv, Nvidia::D
R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size, nvmap_fd));
}
void FreeHandle(u32 handle, Nvidia::Module& nvdrv, Nvidia::DeviceFD nvmap_fd) {
auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
ASSERT(nvmap != nullptr);
R_ASSERT(FreeNvMapHandle(*nvmap, handle, nvmap_fd));
}
constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
constexpr u32 SharedBufferBlockLinearBpp = 4;
@ -186,53 +209,97 @@ FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& fli
FbShareBufferManager::~FbShareBufferManager() = default;
Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id,
u64* out_layer_handle, u64 display_id,
LayerBlending blending) {
std::scoped_lock lk{m_guard};
// Ensure we have not already created a buffer.
R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
// Ensure we haven't already created.
const u64 aruid = owner_process->GetProcessId();
R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied);
// Allocate memory and space for the shared buffer.
Common::ProcessAddress map_address;
R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address),
std::addressof(m_buffer_page_group), m_system,
SharedBufferSize));
// Allocate memory for the shared buffer if needed.
if (!m_buffer_page_group) {
R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system,
SharedBufferSize));
// Record buffer id.
m_buffer_id = m_next_buffer_id++;
// Record display id.
m_display_id = display_id;
}
// Map into process.
Common::ProcessAddress map_address{};
R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
owner_process, m_system));
// Create new session.
auto [it, was_emplaced] = m_sessions.emplace(aruid, FbShareSession{});
auto& session = it->second;
auto& container = m_nvdrv->GetContainer();
m_session_id = container.OpenSession(m_system.ApplicationProcess());
m_nvmap_fd = m_nvdrv->Open("/dev/nvmap", m_session_id);
session.session_id = container.OpenSession(owner_process);
session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id);
// Create an nvmap handle for the buffer and assign the memory to it.
R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, m_nvmap_fd,
map_address, SharedBufferSize));
// Record the display id.
m_display_id = display_id;
R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv,
session.nvmap_fd, map_address, SharedBufferSize));
// Create and open a layer for the display.
m_layer_id = m_flinger.CreateLayer(m_display_id).value();
m_flinger.OpenLayer(m_layer_id);
// Set up the buffer.
m_buffer_id = m_next_buffer_id++;
session.layer_id = m_flinger.CreateLayer(m_display_id, blending).value();
m_flinger.OpenLayer(session.layer_id);
// Get the layer.
VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
VI::Layer* layer = m_flinger.FindLayer(m_display_id, session.layer_id);
ASSERT(layer != nullptr);
// Get the producer and set preallocated buffers.
auto& producer = layer->GetBufferQueue();
MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle);
MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle);
MakeGraphicBuffer(producer, 0, session.buffer_nvmap_handle);
MakeGraphicBuffer(producer, 1, session.buffer_nvmap_handle);
// Assign outputs.
*out_buffer_id = m_buffer_id;
*out_layer_id = m_layer_id;
*out_layer_handle = session.layer_id;
// We succeeded.
R_SUCCEED();
}
void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
std::scoped_lock lk{m_guard};
if (m_buffer_id == 0) {
return;
}
const u64 aruid = owner_process->GetProcessId();
const auto it = m_sessions.find(aruid);
if (it == m_sessions.end()) {
return;
}
auto& session = it->second;
// Destroy the layer.
m_flinger.DestroyLayer(session.layer_id);
// Close nvmap handle.
FreeHandle(session.buffer_nvmap_handle, *m_nvdrv, session.nvmap_fd);
// Close nvmap device.
m_nvdrv->Close(session.nvmap_fd);
// Close session.
auto& container = m_nvdrv->GetContainer();
container.CloseSession(session.session_id);
// Erase.
m_sessions.erase(it);
}
Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
s32* out_nvmap_handle,
SharedMemoryPoolLayout* out_pool_layout,
@ -242,17 +309,18 @@ Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
R_UNLESS(m_sessions.contains(applet_resource_user_id), VI::ResultNotFound);
*out_pool_layout = SharedBufferPoolLayout;
*out_buffer_size = SharedBufferSize;
*out_nvmap_handle = m_buffer_nvmap_handle;
*out_nvmap_handle = m_sessions[applet_resource_user_id].buffer_nvmap_handle;
R_SUCCEED();
}
Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
// Ensure the layer id is valid.
R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound);
R_UNLESS(layer_id > 0, VI::ResultNotFound);
// Get the layer.
VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
@ -309,6 +377,10 @@ Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
android::Status::NoError,
VI::ResultOperationFailed);
ON_RESULT_FAILURE {
producer.CancelBuffer(static_cast<s32>(slot), fence);
};
// Queue the buffer to the producer.
android::QueueBufferInput input{};
android::QueueBufferOutput output{};
@ -342,4 +414,12 @@ Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadab
R_SUCCEED();
}
Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
s32* out_layer_index) {
// TODO
*out_was_written = true;
*out_layer_index = 1;
R_SUCCEED();
}
} // namespace Service::Nvnflinger

View File

@ -3,9 +3,12 @@
#pragma once
#include <map>
#include "common/math_util.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
@ -29,13 +32,18 @@ struct SharedMemoryPoolLayout {
};
static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
struct FbShareSession;
class FbShareBufferManager final {
public:
explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
std::shared_ptr<Nvidia::Module> nvdrv);
~FbShareBufferManager();
Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id);
Result Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle,
u64 display_id, LayerBlending blending);
void Finalize(Kernel::KProcess* owner_process);
Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
u64 applet_resource_user_id);
@ -45,6 +53,8 @@ public:
u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index);
private:
Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
@ -52,11 +62,8 @@ private:
u64 m_next_buffer_id = 1;
u64 m_display_id = 0;
u64 m_buffer_id = 0;
u64 m_layer_id = 0;
u32 m_buffer_nvmap_handle = 0;
SharedMemoryPoolLayout m_pool_layout = {};
Nvidia::DeviceFD m_nvmap_fd = {};
Nvidia::NvCore::SessionId m_session_id = {};
std::map<u64, FbShareSession> m_sessions;
std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
std::mutex m_guard;
@ -65,4 +72,11 @@ private:
std::shared_ptr<Nvidia::Module> m_nvdrv;
};
struct FbShareSession {
Nvidia::DeviceFD nvmap_fd = {};
Nvidia::NvCore::SessionId session_id = {};
u64 layer_id = {};
u32 buffer_nvmap_handle = 0;
};
} // namespace Service::Nvnflinger

View File

@ -86,6 +86,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
.height = igbp_buffer.Height(),
.stride = igbp_buffer.Stride(),
.z_index = 0,
.blending = layer.GetBlending(),
.transform = static_cast<android::BufferTransformFlags>(item.transform),
.crop_rect = item.crop,
.acquire_fence = item.fence,

View File

@ -11,6 +11,18 @@
namespace Service::Nvnflinger {
// hwc_layer_t::blending values
enum class LayerBlending : u32 {
// No blending
None = 0x100,
// ONE / ONE_MINUS_SRC_ALPHA
Premultiplied = 0x105,
// SRC_ALPHA / ONE_MINUS_SRC_ALPHA
Coverage = 0x405,
};
struct HwcLayer {
u32 buffer_handle;
u32 offset;
@ -19,6 +31,7 @@ struct HwcLayer {
u32 height;
u32 stride;
s32 z_index;
LayerBlending blending;
android::BufferTransformFlags transform;
Common::Rectangle<int> crop_rect;
android::Fence acquire_fence;

View File

@ -157,7 +157,7 @@ bool Nvnflinger::CloseDisplay(u64 display_id) {
return true;
}
std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
std::optional<u64> Nvnflinger::CreateLayer(u64 display_id, LayerBlending blending) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
@ -166,13 +166,14 @@ std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
}
const u64 layer_id = next_layer_id++;
CreateLayerAtId(*display, layer_id);
CreateLayerAtId(*display, layer_id, blending);
return layer_id;
}
void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending) {
const auto buffer_id = next_buffer_queue_id++;
display.CreateLayer(layer_id, buffer_id, nvdrv->container);
display.FindLayer(layer_id)->SetBlending(blending);
}
bool Nvnflinger::OpenLayer(u64 layer_id) {

View File

@ -15,6 +15,7 @@
#include "common/thread.h"
#include "core/hle/result.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvnflinger/hwc_layer.h"
namespace Common {
class Event;
@ -72,7 +73,8 @@ public:
/// Creates a layer on the specified display and returns the layer ID.
///
/// If an invalid display ID is specified, then an empty optional is returned.
[[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
[[nodiscard]] std::optional<u64> CreateLayer(u64 display_id,
LayerBlending blending = LayerBlending::None);
/// Opens a layer on all displays for the given layer ID.
bool OpenLayer(u64 layer_id);
@ -128,7 +130,7 @@ private:
[[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
/// Creates a layer with the specified layer ID in the desired display.
void CreateLayerAtId(VI::Display& display, u64 layer_id);
void CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending);
void SplitVSync(std::stop_token stop_token);

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/vi/layer/vi_layer.h"
namespace Service::VI {
@ -8,8 +9,9 @@ namespace Service::VI {
Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
android::BufferQueueProducer& binder_,
std::shared_ptr<android::BufferItemConsumer>&& consumer_)
: layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_},
consumer{std::move(consumer_)}, open{false}, visible{true} {}
: layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move(
consumer_)},
blending{Nvnflinger::LayerBlending::None}, open{false}, visible{true} {}
Layer::~Layer() = default;

View File

@ -14,6 +14,10 @@ class BufferQueueCore;
class BufferQueueProducer;
} // namespace Service::android
namespace Service::Nvnflinger {
enum class LayerBlending : u32;
}
namespace Service::VI {
/// Represents a single display layer.
@ -92,12 +96,21 @@ public:
return !std::exchange(open, true);
}
Nvnflinger::LayerBlending GetBlending() {
return blending;
}
void SetBlending(Nvnflinger::LayerBlending b) {
blending = b;
}
private:
const u64 layer_id;
const u32 binder_id;
android::BufferQueueCore& core;
android::BufferQueueProducer& binder;
std::shared_ptr<android::BufferItemConsumer> consumer;
Service::Nvnflinger::LayerBlending blending;
bool open;
bool visible;
};