summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/core/hle/service/y2r_u.cpp300
3 files changed, 267 insertions, 35 deletions
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index bd2c6a153..6ca8cb78d 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -45,6 +45,7 @@ namespace Log {
SUB(Service, DSP) \
SUB(Service, HID) \
SUB(Service, SOC) \
+ SUB(Service, Y2R) \
CLS(HW) \
SUB(HW, Memory) \
SUB(HW, LCD) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index fd87ddbe6..d720d7fe0 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -65,6 +65,7 @@ enum class Class : ClassType {
Service_DSP, ///< The DSP (DSP control) service
Service_HID, ///< The HID (User input) service
Service_SOC, ///< The SOC (Socket) service
+ Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index edc443611..ce822e990 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -1,85 +1,314 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
+
#include "common/logging/log.h"
#include "core/hle/hle.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/y2r_u.h"
+#include "video_core/utils.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace Y2R_U
namespace Y2R_U {
+enum class InputFormat {
+ /// 8-bit input, with YUV components in separate planes and using 4:2:2 subsampling.
+ YUV422_Indiv8 = 0,
+ /// 8-bit input, with YUV components in separate planes and using 4:2:0 subsampling.
+ YUV420_Indiv8 = 1,
+
+ YUV422_INDIV_16 = 2,
+ YUV420_INDIV_16 = 3,
+ YUV422_BATCH = 4,
+};
+
+enum class OutputFormat {
+ Rgb32 = 0,
+ Rgb24 = 1,
+ Rgb16_555 = 2,
+ Rgb16_565 = 3,
+};
+
+enum class Rotation {
+ None = 0,
+ Clockwise_90 = 1,
+ Clockwise_180 = 2,
+ Clockwise_270 = 3,
+};
+
+enum class BlockAlignment {
+ /// Image is output in linear format suitable for use as a framebuffer.
+ Linear = 0,
+ /// Image is output in tiled PICA format, suitable for use as a texture.
+ Block8x8 = 1,
+};
+
+enum class StandardCoefficient {
+ ITU_Rec601 = 0,
+ ITU_Rec709 = 1,
+ ITU_Rec601_Scaling = 2,
+ ITU_Rec709_Scaling = 3,
+};
+
static Kernel::SharedPtr<Kernel::Event> completion_event;
-/**
- * Y2R_U::IsBusyConversion service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : Whether the current conversion is of type busy conversion (?)
- */
-static void IsBusyConversion(Service::Interface* self) {
+struct ConversionParameters {
+ InputFormat input_format;
+ OutputFormat output_format;
+ Rotation rotation;
+ BlockAlignment alignment;
+ u16 input_line_width;
+ u16 input_lines;
+
+ // Input parameters for the Y (luma) plane
+ VAddr srcY_address;
+ u32 srcY_image_size;
+ u16 srcY_transfer_unit;
+ u16 srcY_stride;
+
+ // Output parameters for the conversion results
+ VAddr dst_address;
+ u32 dst_image_size;
+ u16 dst_transfer_unit;
+ u16 dst_stride;
+};
+
+static ConversionParameters conversion_params;
+
+static void SetInputFormat(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw;;
- cmd_buff[2] = 0;
+ conversion_params.input_format = static_cast<InputFormat>(cmd_buff[1]);
+ LOG_DEBUG(Service_Y2R, "called input_format=%u", conversion_params.input_format);
- LOG_WARNING(Service, "(STUBBED) called");
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+static void SetOutputFormat(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.output_format = static_cast<OutputFormat>(cmd_buff[1]);
+ LOG_DEBUG(Service_Y2R, "called output_format=%u", conversion_params.output_format);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+static void SetRotation(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.rotation = static_cast<Rotation>(cmd_buff[1]);
+ LOG_DEBUG(Service_Y2R, "called rotation=%u", conversion_params.rotation);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+static void SetBlockAlignment(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.alignment = static_cast<BlockAlignment>(cmd_buff[1]);
+ LOG_DEBUG(Service_Y2R, "called alignment=%u", conversion_params.alignment);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
}
/**
- * Y2R_U::GetTransferEndEvent service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 3 : The handle of the completion event
- */
+* Y2R_U::GetTransferEndEvent service function
+* Outputs:
+* 1 : Result of function, 0 on success, otherwise error code
+* 3 : The handle of the completion event
+*/
static void GetTransferEndEvent(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
-
+
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
+ LOG_DEBUG(Service_Y2R, "called");
+}
+
+static void SetSendingY(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.srcY_address = cmd_buff[1];
+ conversion_params.srcY_image_size = cmd_buff[2];
+ conversion_params.srcY_transfer_unit = cmd_buff[3];
+ conversion_params.srcY_stride = cmd_buff[4];
+ u32 src_process_handle = cmd_buff[6];
+ LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
+ "src_process_handle=0x%08X", conversion_params.srcY_image_size,
+ conversion_params.srcY_transfer_unit, conversion_params.srcY_stride, src_process_handle);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+static void SetReceiving(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.dst_address = cmd_buff[1];
+ conversion_params.dst_image_size = cmd_buff[2];
+ conversion_params.dst_transfer_unit = cmd_buff[3];
+ conversion_params.dst_stride = cmd_buff[4];
+ u32 dst_process_handle = cmd_buff[6];
+ LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
+ "dst_process_handle=0x%08X", conversion_params.dst_image_size,
+ conversion_params.dst_transfer_unit, conversion_params.dst_stride,
+ dst_process_handle);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+static void SetInputLineWidth(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.input_line_width = cmd_buff[1];
+ LOG_DEBUG(Service_Y2R, "input_line_width=%u", conversion_params.input_line_width);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+static void SetInputLines(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ conversion_params.input_lines = cmd_buff[1];
+ LOG_DEBUG(Service_Y2R, "input_line_number=%u", conversion_params.input_lines);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
}
-/**
- * Starts a YUV -> RGB conversion
- */
static void StartConversion(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- // TODO(bunnei): This is hack to indicate to the game that the conversion has immediately
- // completed, even though it's not actually implemented yet. This fixes games that would
- // otherwise hang on trying to play moflex videos, which uses the Y2R service.
+ const u8* srcY_buffer = Memory::GetPointer(conversion_params.srcY_address);
+ u8* dst_buffer = Memory::GetPointer(conversion_params.dst_address);
+
+ // TODO: support color and other kinds of conversions
+ ASSERT(conversion_params.input_format == InputFormat::YUV422_Indiv8
+ || conversion_params.input_format == InputFormat::YUV420_Indiv8);
+ ASSERT(conversion_params.output_format == OutputFormat::Rgb24);
+ ASSERT(conversion_params.rotation == Rotation::None);
+ const int bpp = 3;
+
+ switch (conversion_params.alignment) {
+ case BlockAlignment::Linear:
+ {
+ const size_t input_lines = conversion_params.input_lines;
+ const size_t input_line_width = conversion_params.input_line_width;
+ const size_t srcY_stride = conversion_params.srcY_stride;
+ const size_t dst_stride = conversion_params.dst_stride;
+
+ size_t srcY_offset = 0;
+ size_t dst_offset = 0;
+
+ for (size_t line = 0; line < input_lines; ++line) {
+ for (size_t i = 0; i < input_line_width; ++i) {
+ u8 Y = srcY_buffer[srcY_offset];
+ dst_buffer[dst_offset + 0] = Y;
+ dst_buffer[dst_offset + 1] = Y;
+ dst_buffer[dst_offset + 2] = Y;
+
+ srcY_offset += 1;
+ dst_offset += bpp;
+ }
+ srcY_offset += srcY_stride;
+ dst_offset += dst_stride;
+ }
+ break;
+ }
+ case BlockAlignment::Block8x8:
+ {
+ const size_t input_lines = conversion_params.input_lines;
+ const size_t input_line_width = conversion_params.input_line_width;
+ const size_t srcY_stride = conversion_params.srcY_stride;
+ const size_t dst_transfer_unit = conversion_params.dst_transfer_unit;
+ const size_t dst_stride = conversion_params.dst_stride;
+
+ size_t srcY_offset = 0;
+ size_t dst_tile_line_offs = 0;
+
+ const size_t tile_size = 8 * 8 * bpp;
+
+ for (size_t line = 0; line < input_lines;) {
+ size_t tile_y = line / 8;
+ size_t max_line = line + 8;
+
+ for (; line < max_line; ++line) {
+ for (size_t x = 0; x < input_line_width; ++x) {
+ size_t tile_x = x / 8;
+
+ size_t dst_tile_offs = dst_tile_line_offs + tile_x * tile_size;
+ size_t tile_i = VideoCore::MortonInterleave((u32)x, (u32)line);
+
+ size_t dst_offset = dst_tile_offs + tile_i * bpp;
+
+ u8 Y = srcY_buffer[srcY_offset];
+ dst_buffer[dst_offset + 0] = Y;
+ dst_buffer[dst_offset + 1] = Y;
+ dst_buffer[dst_offset + 2] = Y;
+
+ srcY_offset += 1;
+ }
+
+ srcY_offset += srcY_stride;
+ }
+
+ dst_tile_line_offs += dst_transfer_unit + dst_stride;
+ }
+ break;
+ }
+ }
+ LOG_DEBUG(Service_Y2R, "called");
completion_event->Signal();
- LOG_WARNING(Service, "(STUBBED) called, expect blank video (MOFLEX) output!");
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+}
+
+/**
+* Y2R_U::IsBusyConversion service function
+* Outputs:
+* 1 : Result of function, 0 on success, otherwise error code
+* 2 : 1 if there's a conversion running, otherwise 0.
+*/
+static void IsBusyConversion(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = 0; // StartConversion always finishes immediately
+ LOG_DEBUG(Service_Y2R, "called");
+}
+
+static void PingProcess(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = 0;
+ LOG_WARNING(Service_Y2R, "(STUBBED) called");
}
const Interface::FunctionInfo FunctionTable[] = {
- {0x00010040, nullptr, "SetInputFormat"},
- {0x00030040, nullptr, "SetOutputFormat"},
- {0x00050040, nullptr, "SetRotation"},
- {0x00070040, nullptr, "SetBlockAlignment"},
+ {0x00010040, SetInputFormat, "SetInputFormat"},
+ {0x00030040, SetOutputFormat, "SetOutputFormat"},
+ {0x00050040, SetRotation, "SetRotation"},
+ {0x00070040, SetBlockAlignment, "SetBlockAlignment"},
{0x000D0040, nullptr, "SetTransferEndInterrupt"},
{0x000F0000, GetTransferEndEvent, "GetTransferEndEvent"},
- {0x00100102, nullptr, "SetSendingY"},
+ {0x00100102, SetSendingY, "SetSendingY"},
{0x00110102, nullptr, "SetSendingU"},
{0x00120102, nullptr, "SetSendingV"},
- {0x00180102, nullptr, "SetReceiving"},
- {0x001A0040, nullptr, "SetInputLineWidth"},
- {0x001C0040, nullptr, "SetInputLines"},
+ {0x00180102, SetReceiving, "SetReceiving"},
+ {0x001A0040, SetInputLineWidth, "SetInputLineWidth"},
+ {0x001C0040, SetInputLines, "SetInputLines"},
{0x00200040, nullptr, "SetStandardCoefficient"},
{0x00220040, nullptr, "SetAlpha"},
{0x00260000, StartConversion, "StartConversion"},
{0x00270000, nullptr, "StopConversion"},
{0x00280000, IsBusyConversion, "IsBusyConversion"},
- {0x002A0000, nullptr, "PingProcess"},
+ {0x002A0000, PingProcess, "PingProcess"},
{0x002B0000, nullptr, "DriverInitialize"},
- {0x002C0000, nullptr, "DriverFinalize"}
+ {0x002C0000, nullptr, "DriverFinalize"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -87,8 +316,9 @@ const Interface::FunctionInfo FunctionTable[] = {
Interface::Interface() {
completion_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "Y2R:Completed");
+ std::memset(&conversion_params, 0, sizeof(conversion_params));
Register(FunctionTable);
}
-
+
} // namespace