1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:08:12 +00:00
serenity/Kernel/Graphics/VirtIOGPU/DisplayConnector.cpp
Liav A 883b0f1390 Kernel/Graphics: Restore VirtIO GPU framebuffer console functionality
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.
2022-12-31 05:13:21 -07:00

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);
}
}