mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:08:12 +00:00

This has been done in multiple ways: - Each time we modeset the resolution via the VirtIOGPU DisplayConnector we ensure that the framebuffer is updated with the new resolution. - Each time the cursor is updated we ensure that the framebuffer console is marked dirty so the IO Work Queue task which is scheduled to check if it is dirty, will flush the surface. - We only initialize a framebuffer console after we ensure that at the very least a DisplayConnector has being set with a known resolution. - We only call GenericFramebufferConsole::enable() when enabling the console after the important variables of the console (m_width, m_pitch and m_height) have been set.
260 lines
10 KiB
C++
260 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <Kernel/API/VirGL.h>
|
|
#include <Kernel/Devices/DeviceManagement.h>
|
|
#include <Kernel/Graphics/GraphicsManagement.h>
|
|
#include <Kernel/Graphics/VirtIOGPU/Console.h>
|
|
#include <Kernel/Graphics/VirtIOGPU/DisplayConnector.h>
|
|
#include <Kernel/Graphics/VirtIOGPU/GraphicsAdapter.h>
|
|
#include <Kernel/Graphics/VirtIOGPU/Protocol.h>
|
|
#include <Kernel/Random.h>
|
|
|
|
namespace Kernel {
|
|
|
|
NonnullLockRefPtr<VirtIODisplayConnector> VirtIODisplayConnector::must_create(VirtIOGraphicsAdapter& graphics_adapter, Graphics::VirtIOGPU::ScanoutID scanout_id)
|
|
{
|
|
auto device_or_error = DeviceManagement::try_create_device<VirtIODisplayConnector>(graphics_adapter, scanout_id);
|
|
VERIFY(!device_or_error.is_error());
|
|
auto connector = device_or_error.release_value();
|
|
return connector;
|
|
}
|
|
|
|
static_assert((MAX_VIRTIOGPU_RESOLUTION_WIDTH * MAX_VIRTIOGPU_RESOLUTION_HEIGHT * sizeof(u32) * 2) % PAGE_SIZE == 0);
|
|
|
|
VirtIODisplayConnector::VirtIODisplayConnector(VirtIOGraphicsAdapter& graphics_adapter, Graphics::VirtIOGPU::ScanoutID scanout_id)
|
|
: DisplayConnector((MAX_VIRTIOGPU_RESOLUTION_WIDTH * MAX_VIRTIOGPU_RESOLUTION_HEIGHT * sizeof(u32) * 2), false)
|
|
, m_graphics_adapter(graphics_adapter)
|
|
, m_scanout_id(scanout_id)
|
|
{
|
|
}
|
|
|
|
void VirtIODisplayConnector::initialize_console(Badge<VirtIOGraphicsAdapter>)
|
|
{
|
|
m_console = Kernel::Graphics::VirtIOGPU::Console::initialize(*this);
|
|
GraphicsManagement::the().set_console(*m_console);
|
|
}
|
|
|
|
void VirtIODisplayConnector::set_safe_mode_setting_after_initialization(Badge<VirtIOGraphicsAdapter>)
|
|
{
|
|
MUST(set_safe_mode_setting());
|
|
}
|
|
|
|
ErrorOr<void> VirtIODisplayConnector::set_mode_setting(ModeSetting const& mode_setting)
|
|
{
|
|
SpinlockLocker locker(m_modeset_lock);
|
|
if (mode_setting.horizontal_active > MAX_VIRTIOGPU_RESOLUTION_WIDTH || mode_setting.vertical_active > MAX_VIRTIOGPU_RESOLUTION_HEIGHT)
|
|
return Error::from_errno(ENOTSUP);
|
|
|
|
auto& info = m_display_info;
|
|
info.rect = {
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = (u32)mode_setting.horizontal_active,
|
|
.height = (u32)mode_setting.vertical_active,
|
|
};
|
|
|
|
TRY(m_graphics_adapter->mode_set_resolution({}, *this, mode_setting.horizontal_active, mode_setting.vertical_active));
|
|
|
|
if (m_console)
|
|
m_console->set_resolution(info.rect.width, info.rect.height, info.rect.width * sizeof(u32));
|
|
DisplayConnector::ModeSetting mode_set {
|
|
.horizontal_stride = info.rect.width * sizeof(u32),
|
|
.pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware
|
|
.horizontal_active = info.rect.width,
|
|
.horizontal_front_porch_pixels = 0, // Note: There's no horizontal_front_porch_pixels in paravirtualized hardware
|
|
.horizontal_sync_time_pixels = 0, // Note: There's no horizontal_sync_time_pixels in paravirtualized hardware
|
|
.horizontal_blank_pixels = 0, // Note: There's no horizontal_blank_pixels in paravirtualized hardware
|
|
.vertical_active = info.rect.height,
|
|
.vertical_front_porch_lines = 0, // Note: There's no vertical_front_porch_lines in paravirtualized hardware
|
|
.vertical_sync_time_lines = 0, // Note: There's no vertical_sync_time_lines in paravirtualized hardware
|
|
.vertical_blank_lines = 0, // Note: There's no vertical_blank_lines in paravirtualized hardware
|
|
.horizontal_offset = 0,
|
|
.vertical_offset = 0,
|
|
};
|
|
m_current_mode_setting = mode_set;
|
|
|
|
m_display_info.enabled = 1;
|
|
return {};
|
|
}
|
|
ErrorOr<void> VirtIODisplayConnector::set_safe_mode_setting()
|
|
{
|
|
DisplayConnector::ModeSetting safe_mode_setting {
|
|
.horizontal_stride = 1024 * sizeof(u32),
|
|
.pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware
|
|
.horizontal_active = 1024,
|
|
.horizontal_front_porch_pixels = 0, // Note: There's no horizontal_front_porch_pixels in paravirtualized hardware
|
|
.horizontal_sync_time_pixels = 0, // Note: There's no horizontal_sync_time_pixels in paravirtualized hardware
|
|
.horizontal_blank_pixels = 0, // Note: There's no horizontal_blank_pixels in paravirtualized hardware
|
|
.vertical_active = 768,
|
|
.vertical_front_porch_lines = 0, // Note: There's no vertical_front_porch_lines in paravirtualized hardware
|
|
.vertical_sync_time_lines = 0, // Note: There's no vertical_sync_time_lines in paravirtualized hardware
|
|
.vertical_blank_lines = 0, // Note: There's no vertical_blank_lines in paravirtualized hardware
|
|
.horizontal_offset = 0,
|
|
.vertical_offset = 0,
|
|
};
|
|
return set_mode_setting(safe_mode_setting);
|
|
}
|
|
|
|
ErrorOr<void> VirtIODisplayConnector::set_y_offset(size_t)
|
|
{
|
|
// NOTE (FIXME?): We don't do double buffering because when using double buffering,
|
|
// perfomance visually looks terrible (everything look sluggish) compared to not using it,
|
|
// so until we figure out why (and we might not figure this and double buffering is simply not needed)
|
|
// this happens, we simply don't support it.
|
|
return Error::from_errno(ENOTSUP);
|
|
}
|
|
ErrorOr<void> VirtIODisplayConnector::unblank()
|
|
{
|
|
return Error::from_errno(ENOTIMPL);
|
|
}
|
|
|
|
ErrorOr<void> VirtIODisplayConnector::flush_rectangle(size_t buffer_index, FBRect const& rect)
|
|
{
|
|
VERIFY(m_flushing_lock.is_locked());
|
|
if (!is_valid_buffer_index(buffer_index))
|
|
return Error::from_errno(EINVAL);
|
|
SpinlockLocker locker(m_graphics_adapter->operation_lock());
|
|
Graphics::VirtIOGPU::Protocol::Rect dirty_rect {
|
|
.x = rect.x,
|
|
.y = rect.y,
|
|
.width = rect.width,
|
|
.height = rect.height
|
|
};
|
|
|
|
TRY(m_graphics_adapter->transfer_framebuffer_data_to_host({}, *this, dirty_rect, true));
|
|
// Flushing directly to screen
|
|
TRY(flush_displayed_image(dirty_rect, true));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> VirtIODisplayConnector::flush_first_surface()
|
|
{
|
|
VERIFY(m_flushing_lock.is_locked());
|
|
SpinlockLocker locker(m_graphics_adapter->operation_lock());
|
|
Graphics::VirtIOGPU::Protocol::Rect dirty_rect {
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = m_display_info.rect.width,
|
|
.height = m_display_info.rect.height
|
|
};
|
|
|
|
TRY(m_graphics_adapter->transfer_framebuffer_data_to_host({}, *this, dirty_rect, true));
|
|
// Flushing directly to screen
|
|
TRY(flush_displayed_image(dirty_rect, true));
|
|
return {};
|
|
}
|
|
|
|
void VirtIODisplayConnector::enable_console()
|
|
{
|
|
VERIFY(m_control_lock.is_locked());
|
|
VERIFY(m_console);
|
|
m_console->enable();
|
|
}
|
|
|
|
void VirtIODisplayConnector::disable_console()
|
|
{
|
|
VERIFY(m_control_lock.is_locked());
|
|
VERIFY(m_console);
|
|
m_console->disable();
|
|
}
|
|
|
|
void VirtIODisplayConnector::set_edid_bytes(Badge<VirtIOGraphicsAdapter>, Array<u8, 128> const& edid_bytes)
|
|
{
|
|
DisplayConnector::set_edid_bytes(edid_bytes);
|
|
}
|
|
|
|
Graphics::VirtIOGPU::Protocol::DisplayInfoResponse::Display VirtIODisplayConnector::display_information(Badge<VirtIOGraphicsAdapter>) const
|
|
{
|
|
return m_display_info;
|
|
}
|
|
|
|
void VirtIODisplayConnector::clear_to_black()
|
|
{
|
|
size_t width = m_display_info.rect.width;
|
|
size_t height = m_display_info.rect.height;
|
|
u8* data = framebuffer_data();
|
|
for (size_t i = 0; i < width * height; ++i) {
|
|
data[4 * i + 0] = 0x00;
|
|
data[4 * i + 1] = 0x00;
|
|
data[4 * i + 2] = 0x00;
|
|
data[4 * i + 3] = 0xff;
|
|
}
|
|
}
|
|
|
|
void VirtIODisplayConnector::draw_ntsc_test_pattern(Badge<VirtIOGraphicsAdapter>)
|
|
{
|
|
constexpr u8 colors[12][4] = {
|
|
{ 0xff, 0xff, 0xff, 0xff }, // White
|
|
{ 0x00, 0xff, 0xff, 0xff }, // Primary + Composite colors
|
|
{ 0xff, 0xff, 0x00, 0xff },
|
|
{ 0x00, 0xff, 0x00, 0xff },
|
|
{ 0xff, 0x00, 0xff, 0xff },
|
|
{ 0x00, 0x00, 0xff, 0xff },
|
|
{ 0xff, 0x00, 0x00, 0xff },
|
|
{ 0xba, 0x01, 0x5f, 0xff }, // Dark blue
|
|
{ 0x8d, 0x3d, 0x00, 0xff }, // Purple
|
|
{ 0x22, 0x22, 0x22, 0xff }, // Shades of gray
|
|
{ 0x10, 0x10, 0x10, 0xff },
|
|
{ 0x00, 0x00, 0x00, 0xff },
|
|
};
|
|
size_t width = m_display_info.rect.width;
|
|
size_t height = m_display_info.rect.height;
|
|
u8* data = framebuffer_data();
|
|
// Draw NTSC test card
|
|
for (size_t i = 0; i < 2; ++i) {
|
|
for (size_t y = 0; y < height; ++y) {
|
|
for (size_t x = 0; x < width; ++x) {
|
|
size_t color = 0;
|
|
if (3 * y < 2 * height) {
|
|
// Top 2/3 of image is 7 vertical stripes of color spectrum
|
|
color = (7 * x) / width;
|
|
} else if (4 * y < 3 * height) {
|
|
// 2/3 mark to 3/4 mark is backwards color spectrum alternating with black
|
|
auto segment = (7 * x) / width;
|
|
color = segment % 2 ? 10 : 6 - segment;
|
|
} else {
|
|
if (28 * x < 5 * width) {
|
|
color = 8;
|
|
} else if (28 * x < 10 * width) {
|
|
color = 0;
|
|
} else if (28 * x < 15 * width) {
|
|
color = 7;
|
|
} else if (28 * x < 20 * width) {
|
|
color = 10;
|
|
} else if (7 * x < 6 * width) {
|
|
// Grayscale gradient
|
|
color = 26 - ((21 * x) / width);
|
|
} else {
|
|
// Solid black
|
|
color = 10;
|
|
}
|
|
}
|
|
u8* pixel = &data[4 * (y * width + x)];
|
|
for (int i = 0; i < 4; ++i) {
|
|
pixel[i] = colors[color][i];
|
|
}
|
|
}
|
|
}
|
|
data = data + (width * height * sizeof(u32));
|
|
}
|
|
dbgln_if(VIRTIO_DEBUG, "Finish drawing the pattern");
|
|
}
|
|
|
|
ErrorOr<void> VirtIODisplayConnector::flush_displayed_image(Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer)
|
|
{
|
|
VERIFY(m_graphics_adapter->operation_lock().is_locked());
|
|
TRY(m_graphics_adapter->flush_displayed_image({}, *this, dirty_rect, main_buffer));
|
|
return {};
|
|
}
|
|
|
|
void VirtIODisplayConnector::set_dirty_displayed_rect(Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer)
|
|
{
|
|
VERIFY(m_graphics_adapter->operation_lock().is_locked());
|
|
m_graphics_adapter->set_dirty_displayed_rect({}, *this, dirty_rect, main_buffer);
|
|
}
|
|
|
|
}
|