From 9108482888ae66644e253fb104411746dc82b635 Mon Sep 17 00:00:00 2001
From: Yuri Kunde Schlesner <yuriks@yuriks.net>
Date: Thu, 21 May 2015 23:27:48 -0300
Subject: [PATCH] Service::Y2R: Support for grayscale decoding of specific
 formats

Implements unrotated planar YUV 4:2:0 -> RGB24 conversions in Y2R.
Currently only the Y (luma) channel is used, so the results don't
contain color. This will be added in a later PR at some point.

This is enough to get all currently know Moflex videos to decode. (Some
don't display on-screen due to seemingly unrelated reasons.)

Thanks to @archshift for doing the initial implementation which I
cleaned up and then fixed the 8x8 block mode.
---
 src/common/logging/backend.cpp |   1 +
 src/common/logging/log.h       |   1 +
 src/core/hle/service/y2r_u.cpp | 302 +++++++++++++++++++++++++++++----
 3 files changed, 268 insertions(+), 36 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.
-    completion_event->Signal();
+    const u8* srcY_buffer = Memory::GetPointer(conversion_params.srcY_address);
+    u8* dst_buffer = Memory::GetPointer(conversion_params.dst_address);
 
-    LOG_WARNING(Service, "(STUBBED) called, expect blank video (MOFLEX) output!");
+    // 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();
 
     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