9 changed files with 452 additions and 363 deletions
-
4src/core/CMakeLists.txt
-
96src/core/hle/service/nvflinger/buffer_queue.cpp
-
82src/core/hle/service/nvflinger/buffer_queue.h
-
161src/core/hle/service/nvflinger/nvflinger.cpp
-
84src/core/hle/service/nvflinger/nvflinger.h
-
250src/core/hle/service/vi/vi.cpp
-
130src/core/hle/service/vi/vi.h
-
2src/core/hle/service/vi/vi_m.cpp
-
6src/core/hle/service/vi/vi_m.h
@ -0,0 +1,96 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
|
||||
|
#include "common/alignment.h"
|
||||
|
#include "common/scope_exit.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
|
|
||||
|
namespace Service { |
||||
|
namespace NVFlinger { |
||||
|
|
||||
|
BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { |
||||
|
native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle"); |
||||
|
} |
||||
|
|
||||
|
void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) { |
||||
|
Buffer buffer{}; |
||||
|
buffer.slot = slot; |
||||
|
buffer.igbp_buffer = igbp_buffer; |
||||
|
buffer.status = Buffer::Status::Free; |
||||
|
|
||||
|
LOG_WARNING(Service, "Adding graphics buffer %u", slot); |
||||
|
|
||||
|
queue.emplace_back(buffer); |
||||
|
} |
||||
|
|
||||
|
u32 BufferQueue::DequeueBuffer(u32 pixel_format, u32 width, u32 height) { |
||||
|
auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { |
||||
|
// Only consider free buffers. Buffers become free once again after they've been Acquired
|
||||
|
// and Released by the compositor, see the NVFlinger::Compose method.
|
||||
|
if (buffer.status != Buffer::Status::Free) |
||||
|
return false; |
||||
|
|
||||
|
// Make sure that the parameters match.
|
||||
|
auto& igbp_buffer = buffer.igbp_buffer; |
||||
|
return igbp_buffer.format == pixel_format && igbp_buffer.width == width && |
||||
|
igbp_buffer.height == height; |
||||
|
}); |
||||
|
ASSERT(itr != queue.end()); |
||||
|
|
||||
|
itr->status = Buffer::Status::Dequeued; |
||||
|
return itr->slot; |
||||
|
} |
||||
|
|
||||
|
const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { |
||||
|
auto itr = std::find_if(queue.begin(), queue.end(), |
||||
|
[&](const Buffer& buffer) { return buffer.slot == slot; }); |
||||
|
ASSERT(itr != queue.end()); |
||||
|
ASSERT(itr->status == Buffer::Status::Dequeued); |
||||
|
return itr->igbp_buffer; |
||||
|
} |
||||
|
|
||||
|
void BufferQueue::QueueBuffer(u32 slot) { |
||||
|
auto itr = std::find_if(queue.begin(), queue.end(), |
||||
|
[&](const Buffer& buffer) { return buffer.slot == slot; }); |
||||
|
ASSERT(itr != queue.end()); |
||||
|
ASSERT(itr->status == Buffer::Status::Dequeued); |
||||
|
itr->status = Buffer::Status::Queued; |
||||
|
} |
||||
|
|
||||
|
boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() { |
||||
|
auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) { |
||||
|
return buffer.status == Buffer::Status::Queued; |
||||
|
}); |
||||
|
if (itr == queue.end()) |
||||
|
return boost::none; |
||||
|
itr->status = Buffer::Status::Acquired; |
||||
|
return *itr; |
||||
|
} |
||||
|
|
||||
|
void BufferQueue::ReleaseBuffer(u32 slot) { |
||||
|
auto itr = std::find_if(queue.begin(), queue.end(), |
||||
|
[&](const Buffer& buffer) { return buffer.slot == slot; }); |
||||
|
ASSERT(itr != queue.end()); |
||||
|
ASSERT(itr->status == Buffer::Status::Acquired); |
||||
|
itr->status = Buffer::Status::Free; |
||||
|
} |
||||
|
|
||||
|
u32 BufferQueue::Query(QueryType type) { |
||||
|
LOG_WARNING(Service, "(STUBBED) called type=%u", static_cast<u32>(type)); |
||||
|
switch (type) { |
||||
|
case QueryType::NativeWindowFormat: |
||||
|
// TODO(Subv): Use an enum for this
|
||||
|
static constexpr u32 FormatABGR8 = 1; |
||||
|
return FormatABGR8; |
||||
|
} |
||||
|
|
||||
|
UNIMPLEMENTED(); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
} // namespace NVFlinger
|
||||
|
} // namespace Service
|
||||
@ -0,0 +1,82 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <boost/optional.hpp> |
||||
|
#include "common/swap.h" |
||||
|
#include "core/hle/kernel/event.h" |
||||
|
|
||||
|
namespace CoreTiming { |
||||
|
struct EventType; |
||||
|
} |
||||
|
|
||||
|
namespace Service { |
||||
|
namespace NVFlinger { |
||||
|
|
||||
|
struct IGBPBuffer { |
||||
|
u32_le magic; |
||||
|
u32_le width; |
||||
|
u32_le height; |
||||
|
u32_le stride; |
||||
|
u32_le format; |
||||
|
u32_le usage; |
||||
|
INSERT_PADDING_WORDS(1); |
||||
|
u32_le index; |
||||
|
INSERT_PADDING_WORDS(3); |
||||
|
u32_le gpu_buffer_id; |
||||
|
INSERT_PADDING_WORDS(17); |
||||
|
u32_le nvmap_handle; |
||||
|
u32_le offset; |
||||
|
INSERT_PADDING_WORDS(60); |
||||
|
}; |
||||
|
|
||||
|
static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size"); |
||||
|
|
||||
|
class BufferQueue final { |
||||
|
public: |
||||
|
enum class QueryType { |
||||
|
NativeWindowWidth = 0, |
||||
|
NativeWindowHeight = 1, |
||||
|
NativeWindowFormat = 2, |
||||
|
}; |
||||
|
|
||||
|
BufferQueue(u32 id, u64 layer_id); |
||||
|
~BufferQueue() = default; |
||||
|
|
||||
|
struct Buffer { |
||||
|
enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 }; |
||||
|
|
||||
|
u32 slot; |
||||
|
Status status = Status::Free; |
||||
|
IGBPBuffer igbp_buffer; |
||||
|
}; |
||||
|
|
||||
|
void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer); |
||||
|
u32 DequeueBuffer(u32 pixel_format, u32 width, u32 height); |
||||
|
const IGBPBuffer& RequestBuffer(u32 slot) const; |
||||
|
void QueueBuffer(u32 slot); |
||||
|
boost::optional<const Buffer&> AcquireBuffer(); |
||||
|
void ReleaseBuffer(u32 slot); |
||||
|
u32 Query(QueryType type); |
||||
|
|
||||
|
u32 GetId() const { |
||||
|
return id; |
||||
|
} |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> GetNativeHandle() const { |
||||
|
return native_handle; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
u32 id; |
||||
|
u64 layer_id; |
||||
|
|
||||
|
std::vector<Buffer> queue; |
||||
|
Kernel::SharedPtr<Kernel::Event> native_handle; |
||||
|
}; |
||||
|
|
||||
|
} // namespace NVFlinger |
||||
|
} // namespace Service |
||||
@ -0,0 +1,161 @@ |
|||||
|
// Copyright 2018 yuzu emulator team
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
|
||||
|
#include "common/alignment.h"
|
||||
|
#include "common/scope_exit.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||
|
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
|
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
|
#include "video_core/renderer_base.h"
|
||||
|
#include "video_core/video_core.h"
|
||||
|
|
||||
|
namespace Service { |
||||
|
namespace NVFlinger { |
||||
|
|
||||
|
constexpr size_t SCREEN_REFRESH_RATE = 60; |
||||
|
constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); |
||||
|
|
||||
|
NVFlinger::NVFlinger() { |
||||
|
// Add the different displays to the list of displays.
|
||||
|
Display default_{0, "Default"}; |
||||
|
Display external{1, "External"}; |
||||
|
Display edid{2, "Edid"}; |
||||
|
Display internal{3, "Internal"}; |
||||
|
|
||||
|
displays.emplace_back(default_); |
||||
|
displays.emplace_back(external); |
||||
|
displays.emplace_back(edid); |
||||
|
displays.emplace_back(internal); |
||||
|
|
||||
|
// Schedule the screen composition events
|
||||
|
composition_event = |
||||
|
CoreTiming::RegisterEvent("ScreenCompositioin", [this](u64 userdata, int cycles_late) { |
||||
|
Compose(); |
||||
|
CoreTiming::ScheduleEvent(frame_ticks - cycles_late, composition_event); |
||||
|
}); |
||||
|
|
||||
|
CoreTiming::ScheduleEvent(frame_ticks, composition_event); |
||||
|
} |
||||
|
|
||||
|
NVFlinger::~NVFlinger() { |
||||
|
CoreTiming::UnscheduleEvent(composition_event, 0); |
||||
|
} |
||||
|
|
||||
|
u64 NVFlinger::OpenDisplay(const std::string& name) { |
||||
|
LOG_WARNING(Service, "Opening display %s", name.c_str()); |
||||
|
|
||||
|
// TODO(Subv): Currently we only support the Default display.
|
||||
|
ASSERT(name == "Default"); |
||||
|
|
||||
|
auto itr = std::find_if(displays.begin(), displays.end(), |
||||
|
[&](const Display& display) { return display.name == name; }); |
||||
|
|
||||
|
ASSERT(itr != displays.end()); |
||||
|
|
||||
|
return itr->id; |
||||
|
} |
||||
|
|
||||
|
u64 NVFlinger::CreateLayer(u64 display_id) { |
||||
|
auto& display = GetDisplay(display_id); |
||||
|
|
||||
|
ASSERT_MSG(display.layers.empty(), "Only one layer is supported per display at the moment"); |
||||
|
|
||||
|
u64 layer_id = next_layer_id++; |
||||
|
u32 buffer_queue_id = next_buffer_queue_id++; |
||||
|
auto buffer_queue = std::make_shared<BufferQueue>(buffer_queue_id, layer_id); |
||||
|
display.layers.emplace_back(layer_id, buffer_queue); |
||||
|
buffer_queues.emplace_back(std::move(buffer_queue)); |
||||
|
return layer_id; |
||||
|
} |
||||
|
|
||||
|
u32 NVFlinger::GetBufferQueueId(u64 display_id, u64 layer_id) { |
||||
|
const auto& layer = GetLayer(display_id, layer_id); |
||||
|
return layer.buffer_queue->GetId(); |
||||
|
} |
||||
|
|
||||
|
Kernel::SharedPtr<Kernel::Event> NVFlinger::GetVsyncEvent(u64 display_id) { |
||||
|
const auto& display = GetDisplay(display_id); |
||||
|
return display.vsync_event; |
||||
|
} |
||||
|
|
||||
|
std::shared_ptr<BufferQueue> NVFlinger::GetBufferQueue(u32 id) const { |
||||
|
auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), |
||||
|
[&](const auto& queue) { return queue->GetId() == id; }); |
||||
|
|
||||
|
ASSERT(itr != buffer_queues.end()); |
||||
|
return *itr; |
||||
|
} |
||||
|
|
||||
|
Display& NVFlinger::GetDisplay(u64 display_id) { |
||||
|
auto itr = std::find_if(displays.begin(), displays.end(), |
||||
|
[&](const Display& display) { return display.id == display_id; }); |
||||
|
|
||||
|
ASSERT(itr != displays.end()); |
||||
|
return *itr; |
||||
|
} |
||||
|
|
||||
|
Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) { |
||||
|
auto& display = GetDisplay(display_id); |
||||
|
|
||||
|
auto itr = std::find_if(display.layers.begin(), display.layers.end(), |
||||
|
[&](const Layer& layer) { return layer.id == layer_id; }); |
||||
|
|
||||
|
ASSERT(itr != display.layers.end()); |
||||
|
return *itr; |
||||
|
} |
||||
|
|
||||
|
void NVFlinger::Compose() { |
||||
|
for (auto& display : displays) { |
||||
|
// Trigger vsync for this display at the end of drawing
|
||||
|
SCOPE_EXIT({ display.vsync_event->Signal(); }); |
||||
|
|
||||
|
// Don't do anything for displays without layers.
|
||||
|
if (display.layers.empty()) |
||||
|
continue; |
||||
|
|
||||
|
// TODO(Subv): Support more than 1 layer.
|
||||
|
ASSERT_MSG(display.layers.size() == 1, "Max 1 layer per display is supported"); |
||||
|
|
||||
|
Layer& layer = display.layers[0]; |
||||
|
auto& buffer_queue = layer.buffer_queue; |
||||
|
|
||||
|
// Search for a queued buffer and acquire it
|
||||
|
auto buffer = buffer_queue->AcquireBuffer(); |
||||
|
|
||||
|
if (buffer == boost::none) { |
||||
|
// There was no queued buffer to draw, render previous frame
|
||||
|
VideoCore::g_renderer->SwapBuffers({}); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
auto& igbp_buffer = buffer->igbp_buffer; |
||||
|
|
||||
|
// Now send the buffer to the GPU for drawing.
|
||||
|
auto nvdrv = Nvidia::nvdrv.lock(); |
||||
|
ASSERT(nvdrv); |
||||
|
|
||||
|
// TODO(Subv): Support more than just disp0. The display device selection is probably based
|
||||
|
// on which display we're drawing (Default, Internal, External, etc)
|
||||
|
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0"); |
||||
|
ASSERT(nvdisp); |
||||
|
|
||||
|
nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format, |
||||
|
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride); |
||||
|
|
||||
|
buffer_queue->ReleaseBuffer(buffer->slot); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {} |
||||
|
|
||||
|
Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) { |
||||
|
vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event"); |
||||
|
} |
||||
|
|
||||
|
} // namespace NVFlinger
|
||||
|
} // namespace Service
|
||||
@ -0,0 +1,84 @@ |
|||||
|
// Copyright 2018 yuzu emulator team |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <boost/optional.hpp> |
||||
|
#include "core/hle/kernel/event.h" |
||||
|
|
||||
|
namespace CoreTiming { |
||||
|
struct EventType; |
||||
|
} |
||||
|
|
||||
|
namespace Service { |
||||
|
namespace NVFlinger { |
||||
|
|
||||
|
class BufferQueue; |
||||
|
|
||||
|
struct Layer { |
||||
|
Layer(u64 id, std::shared_ptr<BufferQueue> queue); |
||||
|
~Layer() = default; |
||||
|
|
||||
|
u64 id; |
||||
|
std::shared_ptr<BufferQueue> buffer_queue; |
||||
|
}; |
||||
|
|
||||
|
struct Display { |
||||
|
Display(u64 id, std::string name); |
||||
|
~Display() = default; |
||||
|
|
||||
|
u64 id; |
||||
|
std::string name; |
||||
|
|
||||
|
std::vector<Layer> layers; |
||||
|
Kernel::SharedPtr<Kernel::Event> vsync_event; |
||||
|
}; |
||||
|
|
||||
|
class NVFlinger final { |
||||
|
public: |
||||
|
NVFlinger(); |
||||
|
~NVFlinger(); |
||||
|
|
||||
|
/// Opens the specified display and returns the id. |
||||
|
u64 OpenDisplay(const std::string& name); |
||||
|
|
||||
|
/// Creates a layer on the specified display and returns the layer id. |
||||
|
u64 CreateLayer(u64 display_id); |
||||
|
|
||||
|
/// Gets the buffer queue id of the specified layer in the specified display. |
||||
|
u32 GetBufferQueueId(u64 display_id, u64 layer_id); |
||||
|
|
||||
|
/// Gets the vsync event for the specified display. |
||||
|
Kernel::SharedPtr<Kernel::Event> GetVsyncEvent(u64 display_id); |
||||
|
|
||||
|
/// Obtains a buffer queue identified by the id. |
||||
|
std::shared_ptr<BufferQueue> GetBufferQueue(u32 id) const; |
||||
|
|
||||
|
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when |
||||
|
/// finished. |
||||
|
void Compose(); |
||||
|
|
||||
|
private: |
||||
|
/// Returns the display identified by the specified id. |
||||
|
Display& GetDisplay(u64 display_id); |
||||
|
|
||||
|
/// Returns the layer identified by the specified id in the desired display. |
||||
|
Layer& GetLayer(u64 display_id, u64 layer_id); |
||||
|
|
||||
|
std::vector<Display> displays; |
||||
|
std::vector<std::shared_ptr<BufferQueue>> buffer_queues; |
||||
|
|
||||
|
/// Id to use for the next layer that is created, this counter is shared among all displays. |
||||
|
u64 next_layer_id = 1; |
||||
|
/// Id to use for the next buffer queue that is created, this counter is shared among all |
||||
|
/// layers. |
||||
|
u32 next_buffer_queue_id = 1; |
||||
|
|
||||
|
/// CoreTiming event that handles screen composition. |
||||
|
CoreTiming::EventType* composition_event; |
||||
|
}; |
||||
|
|
||||
|
} // namespace NVFlinger |
||||
|
} // namespace Service |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue