mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03:00
Kernel: Much improved BochsVGA (BXVGA) support.
Instead of cowboy-calling the VESA BIOS in the bootloader, find the emulator VGA adapter by scanning the PCI bus. Then set up the desired video mode by sending device commands.
This commit is contained in:
parent
e9f6508ada
commit
731fc5a7c8
Notes:
sideshowbarker
2024-07-19 15:51:15 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/731fc5a7c88
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@
|
||||
*.creator.user.*
|
||||
*.files
|
||||
*.includes
|
||||
*.cflags
|
||||
*.cxxflags
|
||||
Patches
|
||||
|
@ -24,7 +24,7 @@ optramimage1: file=none
|
||||
optramimage2: file=none
|
||||
optramimage3: file=none
|
||||
optramimage4: file=none
|
||||
pci: enabled=1, chipset=i440fx
|
||||
pci: enabled=1, chipset=i440fx, slot1=pcivga
|
||||
vga: extension=vbe, update_freq=60, realtime=0
|
||||
cpu: count=1, ips=4000000, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0
|
||||
cpuid: level=6, stepping=3, model=3, family=6, vendor_string="GenuineIntel", brand_string=" Intel(R) Pentium(R) 4 CPU "
|
||||
|
68
Kernel/BochsVGADevice.cpp
Normal file
68
Kernel/BochsVGADevice.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include <Kernel/BochsVGADevice.h>
|
||||
#include <Kernel/IO.h>
|
||||
#include <Kernel/PCI.h>
|
||||
|
||||
#define VBE_DISPI_IOPORT_INDEX 0x01CE
|
||||
#define VBE_DISPI_IOPORT_DATA 0x01CF
|
||||
|
||||
#define VBE_DISPI_INDEX_ID 0x0
|
||||
#define VBE_DISPI_INDEX_XRES 0x1
|
||||
#define VBE_DISPI_INDEX_YRES 0x2
|
||||
#define VBE_DISPI_INDEX_BPP 0x3
|
||||
#define VBE_DISPI_INDEX_ENABLE 0x4
|
||||
#define VBE_DISPI_INDEX_BANK 0x5
|
||||
#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6
|
||||
#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7
|
||||
#define VBE_DISPI_DISABLED 0x00
|
||||
#define VBE_DISPI_ENABLED 0x01
|
||||
#define VBE_DISPI_LFB_ENABLED 0x40
|
||||
|
||||
static BochsVGADevice* s_the;
|
||||
|
||||
BochsVGADevice& BochsVGADevice::the()
|
||||
{
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
void BochsVGADevice::initialize_statics()
|
||||
{
|
||||
s_the = nullptr;
|
||||
}
|
||||
|
||||
BochsVGADevice::BochsVGADevice()
|
||||
{
|
||||
s_the = this;
|
||||
m_framebuffer_address = PhysicalAddress(find_framebuffer_address());
|
||||
}
|
||||
|
||||
void BochsVGADevice::set_register(word index, word data)
|
||||
{
|
||||
IO::out16(VBE_DISPI_IOPORT_INDEX, index);
|
||||
IO::out16(VBE_DISPI_IOPORT_DATA, data);
|
||||
}
|
||||
|
||||
void BochsVGADevice::set_resolution(int width, int height)
|
||||
{
|
||||
set_register(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
|
||||
set_register(VBE_DISPI_INDEX_XRES, width);
|
||||
set_register(VBE_DISPI_INDEX_YRES, height);
|
||||
set_register(VBE_DISPI_INDEX_VIRT_WIDTH, width);
|
||||
set_register(VBE_DISPI_INDEX_VIRT_HEIGHT, height);
|
||||
set_register(VBE_DISPI_INDEX_BPP, 32);
|
||||
set_register(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED);
|
||||
set_register(VBE_DISPI_INDEX_BANK, 0);
|
||||
}
|
||||
|
||||
dword BochsVGADevice::find_framebuffer_address()
|
||||
{
|
||||
static const PCI::ID bochs_vga_id = { 0x1234, 0x1111 };
|
||||
static const PCI::ID virtualbox_vga_id = { 0x80ee, 0xbeef };
|
||||
dword framebuffer_address = 0;
|
||||
PCI::enumerate_all([&framebuffer_address] (const PCI::Address& address, PCI::ID id) {
|
||||
if (id == bochs_vga_id || id == virtualbox_vga_id) {
|
||||
framebuffer_address = PCI::get_BAR0(address) & 0xfffffff0;
|
||||
kprintf("BochsVGA framebuffer @ PCI %w:%w BAR0=%x\n", id.vendor_id, id.device_id, framebuffer_address);
|
||||
}
|
||||
});
|
||||
return framebuffer_address;
|
||||
}
|
25
Kernel/BochsVGADevice.h
Normal file
25
Kernel/BochsVGADevice.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <AK/AKString.h>
|
||||
#include <Kernel/types.h>
|
||||
|
||||
// FIXME: This should be a BlockDevice once we have BlockDevice.
|
||||
|
||||
class BochsVGADevice {
|
||||
AK_MAKE_ETERNAL
|
||||
public:
|
||||
static BochsVGADevice& the();
|
||||
static void initialize_statics();
|
||||
|
||||
BochsVGADevice();
|
||||
|
||||
PhysicalAddress framebuffer_address() const { return m_framebuffer_address; }
|
||||
void set_resolution(int width, int height);
|
||||
|
||||
private:
|
||||
void set_register(word index, word value);
|
||||
dword find_framebuffer_address();
|
||||
|
||||
PhysicalAddress m_framebuffer_address;
|
||||
};
|
@ -9,43 +9,6 @@ boot:
|
||||
mov ss, ax
|
||||
mov sp, 0xffff
|
||||
|
||||
; get vesa modes
|
||||
mov ax, 0x4f00
|
||||
xor dx, dx
|
||||
mov es, dx
|
||||
mov di, 0xc000
|
||||
mov [es:di], byte 'V'
|
||||
mov [es:di+1], byte 'B'
|
||||
mov [es:di+2], byte 'E'
|
||||
mov [es:di+3], byte '2'
|
||||
int 0x10
|
||||
cmp ax, 0x004f
|
||||
jne fug
|
||||
cmp [es:di], byte 'V'
|
||||
jne fug
|
||||
cmp [es:di+1], byte 'E'
|
||||
jne fug
|
||||
cmp [es:di+2], byte 'S'
|
||||
jne fug
|
||||
cmp [es:di+3], byte 'A'
|
||||
jne fug
|
||||
|
||||
; get vesa info
|
||||
mov ax, 0x4f01
|
||||
mov cx, 0x144
|
||||
xor dx, dx
|
||||
mov es, dx
|
||||
mov di, 0x2000
|
||||
int 0x10
|
||||
cmp ax, 0x004f
|
||||
jne fug
|
||||
|
||||
mov ax, 0x4f02
|
||||
mov bx, 0x4144
|
||||
int 0x10
|
||||
cmp ax, 0x004f
|
||||
jne fug
|
||||
|
||||
push cs
|
||||
pop ds
|
||||
xor bx, bx
|
||||
|
@ -18,7 +18,7 @@ inline word in16(word port)
|
||||
return value;
|
||||
}
|
||||
|
||||
inline dword in32(dword port)
|
||||
inline dword in32(word port)
|
||||
{
|
||||
dword value;
|
||||
asm volatile("inl %1, %0":"=a"(value):"Nd"(port));
|
||||
|
@ -30,6 +30,8 @@ KERNEL_OBJS = \
|
||||
ELFLoader.o \
|
||||
KSyms.o \
|
||||
DevPtsFS.o \
|
||||
BochsVGADevice.o \
|
||||
PCI.o \
|
||||
PS2MouseDevice.o \
|
||||
GUIEventDevice.o
|
||||
|
||||
|
121
Kernel/PCI.cpp
Normal file
121
Kernel/PCI.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include <Kernel/PCI.h>
|
||||
#include <Kernel/IO.h>
|
||||
|
||||
#define PCI_VENDOR_ID 0x00 // word
|
||||
#define PCI_DEVICE_ID 0x02 // word
|
||||
#define PCI_COMMAND 0x04 // word
|
||||
#define PCI_STATUS 0x06 // word
|
||||
#define PCI_REVISION_ID 0x08 // byte
|
||||
#define PCI_PROG_IF 0x09 // byte
|
||||
#define PCI_SUBCLASS 0x0a // byte
|
||||
#define PCI_CLASS 0x0b // byte
|
||||
#define PCI_CACHE_LINE_SIZE 0x0c // byte
|
||||
#define PCI_LATENCY_TIMER 0x0d // byte
|
||||
#define PCI_HEADER_TYPE 0x0e // byte
|
||||
#define PCI_BIST 0x0f // byte
|
||||
#define PCI_BAR0 0x10 // dword
|
||||
#define PCI_BAR1 0x14 // dword
|
||||
#define PCI_BAR2 0x18 // dword
|
||||
#define PCI_BAR3 0x1C // dword
|
||||
#define PCI_BAR4 0x20 // dword
|
||||
#define PCI_BAR5 0x24 // dword
|
||||
#define PCI_INTERRUPT_LINE 0x3C // byte
|
||||
#define PCI_SECONDARY_BUS 0x19 // byte
|
||||
#define PCI_HEADER_TYPE_DEVICE 0
|
||||
#define PCI_HEADER_TYPE_BRIDGE 1
|
||||
#define PCI_TYPE_BRIDGE 0x0604
|
||||
#define PCI_ADDRESS_PORT 0xCF8
|
||||
#define PCI_VALUE_PORT 0xCFC
|
||||
#define PCI_NONE 0xFFFF
|
||||
|
||||
namespace PCI {
|
||||
|
||||
template<typename T>
|
||||
T read_field(Address address, dword field)
|
||||
{
|
||||
IO::out32(PCI_ADDRESS_PORT, address.io_address_for_field(field));
|
||||
if constexpr (sizeof(T) == 4)
|
||||
return IO::in32(PCI_VALUE_PORT);
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return IO::in16(PCI_VALUE_PORT + (field & 2));
|
||||
if constexpr (sizeof(T) == 1)
|
||||
return IO::in8(PCI_VALUE_PORT + (field & 3));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void write_field(Address address, dword field, T value)
|
||||
{
|
||||
IO::out32(PCI_ADDRESS_PORT, address.io_address_for_field(field));
|
||||
if constexpr (sizeof(T) == 4)
|
||||
IO::out32(PCI_VALUE_PORT, value);
|
||||
if constexpr (sizeof(T) == 2)
|
||||
IO::out16(PCI_VALUE_PORT + (field & 2), value);
|
||||
if constexpr (sizeof(T) == 1)
|
||||
IO::out8(PCI_VALUE_PORT + (field & 3), value);
|
||||
}
|
||||
|
||||
word read_type(Address address)
|
||||
{
|
||||
return (read_field<byte>(address, PCI_CLASS) << 8u) | read_field<byte>(address, PCI_SUBCLASS);
|
||||
}
|
||||
|
||||
void enumerate_bus(int type, byte bus, Function<void(Address, ID)>&);
|
||||
|
||||
void enumerate_functions(int type, byte bus, byte slot, byte function, Function<void(Address, ID)>& callback)
|
||||
{
|
||||
Address address(bus, slot, function);
|
||||
if (type == -1 || type == read_type(address))
|
||||
callback(address, { read_field<word>(address, PCI_VENDOR_ID), read_field<word>(address, PCI_DEVICE_ID) });
|
||||
if (read_type(address) == PCI_TYPE_BRIDGE) {
|
||||
byte secondary_bus = read_field<byte>(address, PCI_SECONDARY_BUS);
|
||||
kprintf("PCI: Found secondary bus: %u\n", secondary_bus);
|
||||
ASSERT(secondary_bus != bus);
|
||||
enumerate_bus(type, secondary_bus, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void enumerate_slot(int type, byte bus, byte slot, Function<void(Address, ID)>& callback)
|
||||
{
|
||||
Address address(bus, slot, 0);
|
||||
if (read_field<word>(address, PCI_VENDOR_ID) == PCI_NONE)
|
||||
return;
|
||||
enumerate_functions(type, bus, slot, 0, callback);
|
||||
if (!(read_field<byte>(address, PCI_HEADER_TYPE) & 0x80))
|
||||
return;
|
||||
for (byte function = 1; function < 8; ++function) {
|
||||
Address address(bus, slot, function);
|
||||
if (read_field<word>(address, PCI_VENDOR_ID) != PCI_NONE)
|
||||
enumerate_functions(type, bus, slot, function, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void enumerate_bus(int type, byte bus, Function<void(Address, ID)>& callback)
|
||||
{
|
||||
for (byte slot = 0; slot < 32; ++slot)
|
||||
enumerate_slot(type, bus, slot, callback);
|
||||
}
|
||||
|
||||
dword get_BAR0(Address address) { return read_field<dword>(address, PCI_BAR0); }
|
||||
dword get_BAR1(Address address) { return read_field<dword>(address, PCI_BAR1); }
|
||||
dword get_BAR2(Address address) { return read_field<dword>(address, PCI_BAR2); }
|
||||
dword get_BAR3(Address address) { return read_field<dword>(address, PCI_BAR3); }
|
||||
dword get_BAR4(Address address) { return read_field<dword>(address, PCI_BAR4); }
|
||||
dword get_BAR5(Address address) { return read_field<dword>(address, PCI_BAR5); }
|
||||
|
||||
void enumerate_all(Function<void(Address, ID)> callback)
|
||||
{
|
||||
// Single PCI host controller.
|
||||
if ((read_field<byte>(Address(), PCI_HEADER_TYPE) & 0x80) == 0) {
|
||||
enumerate_bus(-1, 0, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple PCI host controllers.
|
||||
for (byte function = 0; function < 8; ++function) {
|
||||
if (read_field<word>(Address(0, 0, function), PCI_VENDOR_ID) == PCI_NONE)
|
||||
break;
|
||||
enumerate_bus(-1, function, callback);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
48
Kernel/PCI.h
Normal file
48
Kernel/PCI.h
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace PCI {
|
||||
|
||||
struct ID {
|
||||
word vendor_id { 0 };
|
||||
word device_id { 0 };
|
||||
|
||||
bool operator==(const ID& other) const
|
||||
{
|
||||
return vendor_id == other.vendor_id && device_id == other.device_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct Address {
|
||||
Address() { }
|
||||
Address(byte bus, byte slot, byte function) : m_bus(bus), m_slot(slot), m_function(function) { }
|
||||
|
||||
bool is_null() const { return !m_bus && !m_slot && !m_function; }
|
||||
operator bool() const { return !is_null(); }
|
||||
|
||||
byte bus() const { return m_bus; }
|
||||
byte slot() const { return m_slot; }
|
||||
byte function() const { return m_function; }
|
||||
|
||||
dword io_address_for_field(byte field) const
|
||||
{
|
||||
return 0x80000000u | (m_bus << 16u) | (m_slot << 11u) | (m_function << 8u) | (field & 0xfc);
|
||||
}
|
||||
|
||||
private:
|
||||
byte m_bus { 0 };
|
||||
byte m_slot { 0 };
|
||||
byte m_function { 0 };
|
||||
};
|
||||
|
||||
void enumerate_all(Function<void(Address, ID)>);
|
||||
dword get_BAR0(Address);
|
||||
dword get_BAR1(Address);
|
||||
dword get_BAR2(Address);
|
||||
dword get_BAR3(Address);
|
||||
dword get_BAR4(Address);
|
||||
dword get_BAR5(Address);
|
||||
|
||||
}
|
@ -1917,79 +1917,6 @@ clock_t Process::sys$times(tms* times)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct vbe_info_structure {
|
||||
char signature[4]; // must be "VESA" to indicate valid VBE support
|
||||
word version; // VBE version; high byte is major version, low byte is minor version
|
||||
dword oem; // segment:offset pointer to OEM
|
||||
dword capabilities; // bitfield that describes card capabilities
|
||||
dword video_modes; // segment:offset pointer to list of supported video modes
|
||||
word video_memory; // amount of video memory in 64KB blocks
|
||||
word software_rev; // software revision
|
||||
dword vendor; // segment:offset to card vendor string
|
||||
dword product_name; // segment:offset to card model name
|
||||
dword product_rev; // segment:offset pointer to product revision
|
||||
char reserved[222]; // reserved for future expansion
|
||||
char oem_data[256]; // OEM BIOSes store their strings in this area
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct vbe_mode_info_structure {
|
||||
word attributes; // deprecated, only bit 7 should be of interest to you, and it indicates the mode supports a linear frame buffer.
|
||||
byte window_a; // deprecated
|
||||
byte window_b; // deprecated
|
||||
word granularity; // deprecated; used while calculating bank numbers
|
||||
word window_size;
|
||||
word segment_a;
|
||||
word segment_b;
|
||||
dword win_func_ptr; // deprecated; used to switch banks from protected mode without returning to real mode
|
||||
word pitch; // number of bytes per horizontal line
|
||||
word width; // width in pixels
|
||||
word height; // height in pixels
|
||||
byte w_char; // unused...
|
||||
byte y_char; // ...
|
||||
byte planes;
|
||||
byte bpp; // bits per pixel in this mode
|
||||
byte banks; // deprecated; total number of banks in this mode
|
||||
byte memory_model;
|
||||
byte bank_size; // deprecated; size of a bank, almost always 64 KB but may be 16 KB...
|
||||
byte image_pages;
|
||||
byte reserved0;
|
||||
|
||||
byte red_mask;
|
||||
byte red_position;
|
||||
byte green_mask;
|
||||
byte green_position;
|
||||
byte blue_mask;
|
||||
byte blue_position;
|
||||
byte reserved_mask;
|
||||
byte reserved_position;
|
||||
byte direct_color_attributes;
|
||||
|
||||
dword framebuffer; // physical address of the linear frame buffer; write here to draw to the screen
|
||||
dword off_screen_mem_off;
|
||||
word off_screen_mem_size; // size of memory in the framebuffer but not being displayed on the screen
|
||||
byte reserved1[206];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
DisplayInfo Process::get_display_info()
|
||||
{
|
||||
DisplayInfo info;
|
||||
//auto* vinfo = reinterpret_cast<vbe_info_structure*>(0xc000);
|
||||
auto* vmode = reinterpret_cast<vbe_mode_info_structure*>(0x2000);
|
||||
dbgprintf("VESA framebuffer, %ux%u, %u bpp @ P%x\n", vmode->width, vmode->height, vmode->bpp, vmode->framebuffer);
|
||||
dbgprintf("Returning display info in %s<%u>\n", name().characters(), pid());
|
||||
info.width = vmode->width;
|
||||
info.height = vmode->height;
|
||||
info.bpp = vmode->bpp;
|
||||
info.pitch = vmode->pitch;
|
||||
size_t framebuffer_size = info.pitch * info.height;
|
||||
if (!m_display_framebuffer_region) {
|
||||
auto framebuffer_vmo = VMObject::create_framebuffer_wrapper(PhysicalAddress(vmode->framebuffer), framebuffer_size);
|
||||
m_display_framebuffer_region = allocate_region_with_vmo(LinearAddress(0xe0000000), framebuffer_size, move(framebuffer_vmo), 0, "framebuffer", true, true);
|
||||
}
|
||||
info.framebuffer = m_display_framebuffer_region->laddr().as_ptr();
|
||||
return info;
|
||||
}
|
||||
|
||||
int Process::sys$select(const Syscall::SC_select_params* params)
|
||||
{
|
||||
if (!validate_read_typed(params))
|
||||
|
@ -217,7 +217,7 @@ public:
|
||||
int gui$set_window_rect(int window_id, const GUI_Rect*);
|
||||
int gui$set_global_cursor_tracking_enabled(int window_id, bool enabled);
|
||||
|
||||
DisplayInfo get_display_info();
|
||||
DisplayInfo set_video_resolution(int width, int height);
|
||||
|
||||
static void initialize();
|
||||
static void initialize_gui_statics();
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <WindowServer/WSMessageLoop.h>
|
||||
#include <WindowServer/WSWindow.h>
|
||||
#include <WindowServer/WSWindowManager.h>
|
||||
#include <Kernel/BochsVGADevice.h>
|
||||
|
||||
//#define LOG_GUI_SYSCALLS
|
||||
|
||||
@ -271,3 +272,22 @@ void Process::destroy_all_windows()
|
||||
}
|
||||
m_windows.clear();
|
||||
}
|
||||
|
||||
|
||||
DisplayInfo Process::set_video_resolution(int width, int height)
|
||||
{
|
||||
DisplayInfo info;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.bpp = 32;
|
||||
info.pitch = width * 4;
|
||||
size_t framebuffer_size = width * height * 4;
|
||||
if (!m_display_framebuffer_region) {
|
||||
auto framebuffer_vmo = VMObject::create_framebuffer_wrapper(BochsVGADevice::the().framebuffer_address(), framebuffer_size);
|
||||
m_display_framebuffer_region = allocate_region_with_vmo(LinearAddress(0xe0000000), framebuffer_size, move(framebuffer_vmo), 0, "framebuffer", true, true);
|
||||
}
|
||||
info.framebuffer = m_display_framebuffer_region->laddr().as_ptr();
|
||||
|
||||
BochsVGADevice::the().set_resolution(width, height);
|
||||
return info;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "PS2MouseDevice.h"
|
||||
#include "PTYMultiplexer.h"
|
||||
#include "DevPtsFS.h"
|
||||
#include "BochsVGADevice.h"
|
||||
|
||||
//#define SPAWN_GUITEST
|
||||
#define SPAWN_GUITEST2
|
||||
@ -170,11 +171,14 @@ void init()
|
||||
MemoryManager::initialize();
|
||||
|
||||
StringImpl::initialize_globals();
|
||||
BochsVGADevice::initialize_statics();
|
||||
|
||||
PIT::initialize();
|
||||
|
||||
memset(&system, 0, sizeof(system));
|
||||
|
||||
new BochsVGADevice;
|
||||
|
||||
auto new_procfs = ProcFS::create();
|
||||
new_procfs->initialize();
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "WSMessage.h"
|
||||
#include "WSWindowManager.h"
|
||||
#include <AK/Assertions.h>
|
||||
#include <Kernel/IO.h>
|
||||
|
||||
static WSScreen* s_the;
|
||||
|
||||
|
@ -10,6 +10,8 @@ public:
|
||||
WSScreen(RGBA32*, unsigned width, unsigned height);
|
||||
~WSScreen();
|
||||
|
||||
void set_resolution(int width, int height);
|
||||
|
||||
int width() const { return m_width; }
|
||||
int height() const { return m_height; }
|
||||
RGBA32* scanline(int y);
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
void WindowServer_main()
|
||||
{
|
||||
auto info = current->get_display_info();
|
||||
auto info = current->set_video_resolution(1024, 768);
|
||||
|
||||
dbgprintf("Screen is %ux%ux%ubpp\n", info.width, info.height, info.bpp);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user