diff --git a/Userland/Services/WindowServer/CMakeLists.txt b/Userland/Services/WindowServer/CMakeLists.txt index 96d7cde7ef..fb52873f8a 100644 --- a/Userland/Services/WindowServer/CMakeLists.txt +++ b/Userland/Services/WindowServer/CMakeLists.txt @@ -25,6 +25,7 @@ set(SOURCES MultiScaleBitmaps.cpp Overlays.cpp Screen.cpp + HardwareScreenBackend.cpp ScreenLayout.cpp Window.cpp WindowFrame.cpp diff --git a/Userland/Services/WindowServer/HardwareScreenBackend.cpp b/Userland/Services/WindowServer/HardwareScreenBackend.cpp new file mode 100644 index 0000000000..2c25dd8253 --- /dev/null +++ b/Userland/Services/WindowServer/HardwareScreenBackend.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018-2020, the SerenityOS developers. + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "HardwareScreenBackend.h" +#include "ScreenBackend.h" +#include +#include +#include +#include +#include +#include +#include + +namespace WindowServer { + +HardwareScreenBackend::HardwareScreenBackend(String device) + : m_device(move(device)) +{ +} + +ErrorOr HardwareScreenBackend::open() +{ + m_framebuffer_fd = TRY(Core::System::open(m_device.characters(), O_RDWR | O_CLOEXEC)); + + FBProperties properties; + if (fb_get_properties(m_framebuffer_fd, &properties) < 0) + return Error::from_syscall(String::formatted("failed to ioctl {}", m_device), errno); + + m_can_device_flush_buffers = (properties.partial_flushing_support != 0); + m_can_set_head_buffer = (properties.doublebuffer_support != 0); + return {}; +} + +HardwareScreenBackend::~HardwareScreenBackend() +{ + if (m_framebuffer_fd >= 0) { + close(m_framebuffer_fd); + m_framebuffer_fd = -1; + } + if (m_framebuffer) { + MUST(Core::System::munmap(m_framebuffer, m_size_in_bytes)); + + m_framebuffer = nullptr; + m_size_in_bytes = 0; + } +} + +ErrorOr HardwareScreenBackend::set_head_resolution(FBHeadResolution resolution) +{ + auto rc = fb_set_resolution(m_framebuffer_fd, &resolution); + if (rc != 0) + return Error::from_syscall("fb_set_resolution", rc); + return {}; +} + +ErrorOr HardwareScreenBackend::unmap_framebuffer() +{ + if (m_framebuffer) { + size_t previous_size_in_bytes = m_size_in_bytes; + return Core::System::munmap(m_framebuffer, previous_size_in_bytes); + } + return {}; +} + +ErrorOr HardwareScreenBackend::map_framebuffer() +{ + FBHeadProperties properties; + properties.head_index = 0; + int rc = fb_get_head_properties(m_framebuffer_fd, &properties); + if (rc != 0) + return Error::from_syscall("fb_get_head_properties", rc); + m_size_in_bytes = properties.buffer_length; + + m_framebuffer = (Gfx::ARGB32*)TRY(Core::System::mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0)); + + if (m_can_set_head_buffer) { + // Note: fall back to assuming the second buffer starts right after the last line of the first + // Note: for now, this calculation works quite well, so need to defer it to another function + // that does ioctl to figure out the correct offset. If a Framebuffer device ever happens to + // to set the second buffer at different location than this, we might need to consider bringing + // back a function with ioctl to check this. + m_back_buffer_offset = static_cast(properties.pitch) * properties.height; + } else { + m_back_buffer_offset = 0; + } + + return {}; +} + +ErrorOr HardwareScreenBackend::get_head_properties() +{ + FBHeadProperties properties; + properties.head_index = 0; + int rc = fb_get_head_properties(m_framebuffer_fd, &properties); + if (rc != 0) + return Error::from_syscall("fb_get_head_properties", rc); + m_pitch = static_cast(properties.pitch); + return properties; +} + +void HardwareScreenBackend::set_head_buffer(int head_index) +{ + VERIFY(m_can_set_head_buffer); + VERIFY(head_index <= 1 && head_index >= 0); + FBHeadVerticalOffset offset { 0, 0 }; + if (head_index == 1) + offset.offsetted = 1; + int rc = fb_set_head_vertical_offset_buffer(m_framebuffer_fd, &offset); + VERIFY(rc == 0); +} + +ErrorOr HardwareScreenBackend::flush_framebuffer_rects(int buffer_index, Span flush_rects) +{ + int rc = fb_flush_buffers(m_framebuffer_fd, buffer_index, flush_rects.data(), (unsigned)flush_rects.size()); + if (rc == -ENOTSUP) + m_can_device_flush_buffers = false; + else + return Error::from_syscall("fb_flush_buffers", rc); + return {}; +} + +} diff --git a/Userland/Services/WindowServer/HardwareScreenBackend.h b/Userland/Services/WindowServer/HardwareScreenBackend.h new file mode 100644 index 0000000000..c491db7f8b --- /dev/null +++ b/Userland/Services/WindowServer/HardwareScreenBackend.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "ScreenBackend.h" +#include "ScreenLayout.h" +#include +#include +#include +#include + +namespace WindowServer { +class HardwareScreenBackend : public ScreenBackend { +public: + virtual ~HardwareScreenBackend(); + + HardwareScreenBackend(String device); + + virtual ErrorOr open() override; + + virtual void set_head_buffer(int index) override; + + virtual ErrorOr flush_framebuffer_rects(int buffer_index, Span rects) override; + + virtual ErrorOr unmap_framebuffer() override; + virtual ErrorOr map_framebuffer() override; + + virtual ErrorOr set_head_resolution(FBHeadResolution) override; + virtual ErrorOr get_head_properties() override; + + String m_device {}; + int m_framebuffer_fd { -1 }; +}; + +} diff --git a/Userland/Services/WindowServer/ScreenBackend.h b/Userland/Services/WindowServer/ScreenBackend.h new file mode 100644 index 0000000000..2b6de0f3f0 --- /dev/null +++ b/Userland/Services/WindowServer/ScreenBackend.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, kleines Filmröllchen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "ScreenLayout.h" +#include +#include +#include + +namespace WindowServer { + +// Handles low-level device interfacing for the screen. +// ScreenBackend is a thin transparent wrapper around framebuffer-related data which is responsible for setting up this data, +// tearing it down, changing its properties like size, and performing flushes. +// The screen is intended to directly access the members to perform its function, but it only ever reads from anything +// except the data in the framebuffer memory. +class ScreenBackend { +public: + virtual ~ScreenBackend() = default; + + virtual ErrorOr open() = 0; + + virtual void set_head_buffer(int index) = 0; + + virtual ErrorOr flush_framebuffer_rects(int buffer_index, Span rects) = 0; + + virtual ErrorOr unmap_framebuffer() = 0; + virtual ErrorOr map_framebuffer() = 0; + + virtual ErrorOr set_head_resolution(FBHeadResolution) = 0; + virtual ErrorOr get_head_properties() = 0; + + bool m_can_device_flush_buffers { true }; + bool m_can_set_head_buffer { false }; + + Gfx::ARGB32* m_framebuffer { nullptr }; + size_t m_size_in_bytes { 0 }; + size_t m_back_buffer_offset { 0 }; + + int m_pitch { 0 }; +}; + +}