2020-01-24 04:51:36 +00:00
|
|
|
#include "host_display.h"
|
2020-10-21 15:25:33 +00:00
|
|
|
#include "common/assert.h"
|
2020-09-14 19:27:22 +00:00
|
|
|
#include "common/file_system.h"
|
2020-03-15 14:03:49 +00:00
|
|
|
#include "common/log.h"
|
|
|
|
#include "common/string_util.h"
|
2020-11-03 05:58:40 +00:00
|
|
|
#include "common/timer.h"
|
2020-06-07 16:53:37 +00:00
|
|
|
#include "stb_image.h"
|
2020-03-15 14:03:49 +00:00
|
|
|
#include "stb_image_resize.h"
|
|
|
|
#include "stb_image_write.h"
|
2020-11-14 14:16:00 +00:00
|
|
|
#include <cerrno>
|
2020-03-15 14:03:49 +00:00
|
|
|
#include <cmath>
|
|
|
|
#include <cstring>
|
2020-11-26 12:40:58 +00:00
|
|
|
#include <thread>
|
2020-03-15 14:03:49 +00:00
|
|
|
#include <vector>
|
|
|
|
Log_SetChannel(HostDisplay);
|
2020-01-24 04:51:36 +00:00
|
|
|
|
|
|
|
HostDisplayTexture::~HostDisplayTexture() = default;
|
|
|
|
|
|
|
|
HostDisplay::~HostDisplay() = default;
|
|
|
|
|
2020-11-03 05:58:40 +00:00
|
|
|
void HostDisplay::SetDisplayMaxFPS(float max_fps)
|
|
|
|
{
|
|
|
|
m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HostDisplay::ShouldSkipDisplayingFrame()
|
|
|
|
{
|
|
|
|
if (m_display_frame_interval == 0.0f)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const u64 now = Common::Timer::GetValue();
|
|
|
|
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
|
|
|
|
if (diff < m_display_frame_interval)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
m_last_frame_displayed_time = now;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-21 15:25:33 +00:00
|
|
|
u32 HostDisplay::GetDisplayPixelFormatSize(HostDisplayPixelFormat format)
|
|
|
|
{
|
|
|
|
switch (format)
|
|
|
|
{
|
|
|
|
case HostDisplayPixelFormat::RGBA8:
|
|
|
|
case HostDisplayPixelFormat::BGRA8:
|
|
|
|
return 4;
|
|
|
|
|
|
|
|
case HostDisplayPixelFormat::RGBA5551:
|
|
|
|
case HostDisplayPixelFormat::RGB565:
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HostDisplay::SetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, const void* buffer, u32 pitch)
|
|
|
|
{
|
|
|
|
void* map_ptr;
|
|
|
|
u32 map_pitch;
|
|
|
|
if (!BeginSetDisplayPixels(format, width, height, &map_ptr, &map_pitch))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (pitch == map_pitch)
|
|
|
|
{
|
|
|
|
std::memcpy(map_ptr, buffer, height * map_pitch);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const u32 copy_size = width * GetDisplayPixelFormatSize(format);
|
|
|
|
DebugAssert(pitch >= copy_size && map_pitch >= copy_size);
|
|
|
|
|
|
|
|
const u8* src_ptr = static_cast<const u8*>(buffer);
|
|
|
|
u8* dst_ptr = static_cast<u8*>(map_ptr);
|
|
|
|
for (u32 i = 0; i < height; i++)
|
|
|
|
{
|
|
|
|
std::memcpy(dst_ptr, src_ptr, copy_size);
|
|
|
|
src_ptr += pitch;
|
|
|
|
dst_ptr += map_pitch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EndSetDisplayPixels();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-07 16:53:37 +00:00
|
|
|
void HostDisplay::SetSoftwareCursor(std::unique_ptr<HostDisplayTexture> texture, float scale /*= 1.0f*/)
|
|
|
|
{
|
|
|
|
m_cursor_texture = std::move(texture);
|
|
|
|
m_cursor_texture_scale = scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HostDisplay::SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale /*= 1.0f*/)
|
|
|
|
{
|
|
|
|
std::unique_ptr<HostDisplayTexture> tex = CreateTexture(width, height, pixels, stride, false);
|
|
|
|
if (!tex)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
SetSoftwareCursor(std::move(tex), scale);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HostDisplay::SetSoftwareCursor(const char* path, float scale /*= 1.0f*/)
|
|
|
|
{
|
2020-09-14 19:27:22 +00:00
|
|
|
auto fp = FileSystem::OpenManagedCFile(path, "rb");
|
|
|
|
if (!fp)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-07 16:53:37 +00:00
|
|
|
int width, height, file_channels;
|
2020-09-14 19:27:22 +00:00
|
|
|
u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4);
|
2020-06-07 16:53:37 +00:00
|
|
|
if (!pixel_data)
|
|
|
|
{
|
|
|
|
const char* error_reason = stbi_failure_reason();
|
|
|
|
Log_ErrorPrintf("Failed to load image from '%s': %s", path, error_reason ? error_reason : "unknown error");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<HostDisplayTexture> tex = CreateTexture(static_cast<u32>(width), static_cast<u32>(height), pixel_data,
|
|
|
|
sizeof(u32) * static_cast<u32>(width), false);
|
|
|
|
stbi_image_free(pixel_data);
|
|
|
|
if (!tex)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
Log_InfoPrintf("Loaded %dx%d image from '%s' for software cursor", width, height, path);
|
|
|
|
SetSoftwareCursor(std::move(tex), scale);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HostDisplay::ClearSoftwareCursor()
|
|
|
|
{
|
|
|
|
m_cursor_texture.reset();
|
|
|
|
m_cursor_texture_scale = 1.0f;
|
|
|
|
}
|
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, s32* out_left, s32* out_top, s32* out_width,
|
|
|
|
s32* out_height, s32* out_left_padding, s32* out_top_padding, float* out_scale,
|
2020-07-04 12:33:07 +00:00
|
|
|
float* out_y_scale, bool apply_aspect_ratio) const
|
2020-02-28 07:01:01 +00:00
|
|
|
{
|
2020-07-13 00:07:01 +00:00
|
|
|
apply_aspect_ratio = (m_display_aspect_ratio > 0) ? apply_aspect_ratio : false;
|
2020-03-28 15:14:37 +00:00
|
|
|
const float y_scale =
|
2020-07-04 12:33:07 +00:00
|
|
|
apply_aspect_ratio ?
|
|
|
|
((static_cast<float>(m_display_width) / static_cast<float>(m_display_height)) / m_display_aspect_ratio) :
|
|
|
|
1.0f;
|
2020-03-28 15:14:37 +00:00
|
|
|
const float display_width = static_cast<float>(m_display_width);
|
|
|
|
const float display_height = static_cast<float>(m_display_height) * y_scale;
|
|
|
|
const float active_left = static_cast<float>(m_display_active_left);
|
|
|
|
const float active_top = static_cast<float>(m_display_active_top) * y_scale;
|
|
|
|
const float active_width = static_cast<float>(m_display_active_width);
|
|
|
|
const float active_height = static_cast<float>(m_display_active_height) * y_scale;
|
2020-04-25 15:10:46 +00:00
|
|
|
if (out_y_scale)
|
|
|
|
*out_y_scale = y_scale;
|
2020-03-28 15:14:37 +00:00
|
|
|
|
|
|
|
// now fit it within the window
|
2020-02-28 07:01:01 +00:00
|
|
|
const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
|
|
|
|
|
|
|
|
float scale;
|
2020-03-28 15:14:37 +00:00
|
|
|
if ((display_width / display_height) >= window_ratio)
|
2020-01-24 04:51:36 +00:00
|
|
|
{
|
2020-03-28 15:14:37 +00:00
|
|
|
// align in middle vertically
|
|
|
|
scale = static_cast<float>(window_width) / display_width;
|
2020-04-30 15:00:22 +00:00
|
|
|
if (m_display_integer_scaling)
|
|
|
|
scale = std::max(std::floor(scale), 1.0f);
|
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
if (out_left_padding)
|
2020-04-30 15:00:22 +00:00
|
|
|
{
|
|
|
|
if (m_display_integer_scaling)
|
|
|
|
*out_left_padding = std::max<s32>((window_width - static_cast<s32>(display_width * scale)) / 2, 0);
|
|
|
|
else
|
|
|
|
*out_left_padding = 0;
|
|
|
|
}
|
2020-04-25 15:10:46 +00:00
|
|
|
if (out_top_padding)
|
2020-09-10 02:07:30 +00:00
|
|
|
{
|
|
|
|
switch (m_display_alignment)
|
|
|
|
{
|
|
|
|
case Alignment::LeftOrTop:
|
|
|
|
*out_top_padding = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Alignment::Center:
|
|
|
|
*out_top_padding = std::max<s32>((window_height - static_cast<s32>(display_height * scale)) / 2, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Alignment::RightOrBottom:
|
|
|
|
*out_top_padding = std::max<s32>(window_height - static_cast<s32>(display_height * scale), 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-01-24 04:51:36 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-28 15:14:37 +00:00
|
|
|
// align in middle horizontally
|
2020-04-25 15:10:46 +00:00
|
|
|
scale = static_cast<float>(window_height) / display_height;
|
2020-04-30 15:00:22 +00:00
|
|
|
if (m_display_integer_scaling)
|
|
|
|
scale = std::max(std::floor(scale), 1.0f);
|
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
if (out_left_padding)
|
2020-09-10 02:07:30 +00:00
|
|
|
{
|
|
|
|
switch (m_display_alignment)
|
|
|
|
{
|
|
|
|
case Alignment::LeftOrTop:
|
|
|
|
*out_left_padding = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Alignment::Center:
|
|
|
|
*out_left_padding = std::max<s32>((window_width - static_cast<s32>(display_width * scale)) / 2, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Alignment::RightOrBottom:
|
|
|
|
*out_left_padding = std::max<s32>(window_width - static_cast<s32>(display_width * scale), 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
if (out_top_padding)
|
2020-04-30 15:00:22 +00:00
|
|
|
{
|
|
|
|
if (m_display_integer_scaling)
|
|
|
|
*out_top_padding = std::max<s32>((window_height - static_cast<s32>(display_height * scale)) / 2, 0);
|
|
|
|
else
|
|
|
|
*out_top_padding = 0;
|
|
|
|
}
|
2020-01-24 04:51:36 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
*out_width = static_cast<s32>(active_width * scale);
|
|
|
|
*out_height = static_cast<s32>(active_height * scale);
|
|
|
|
*out_left = static_cast<s32>(active_left * scale);
|
|
|
|
*out_top = static_cast<s32>(active_top * scale);
|
|
|
|
if (out_scale)
|
|
|
|
*out_scale = scale;
|
|
|
|
}
|
|
|
|
|
2020-07-04 12:33:07 +00:00
|
|
|
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, s32 top_margin,
|
|
|
|
bool apply_aspect_ratio /* = true */) const
|
2020-04-25 15:10:46 +00:00
|
|
|
{
|
|
|
|
s32 left, top, width, height, left_padding, top_padding;
|
|
|
|
CalculateDrawRect(window_width, window_height - top_margin, &left, &top, &width, &height, &left_padding, &top_padding,
|
2020-07-04 12:33:07 +00:00
|
|
|
nullptr, nullptr, apply_aspect_ratio);
|
2020-04-25 15:10:46 +00:00
|
|
|
return std::make_tuple(left + left_padding, top + top_padding + top_margin, width, height);
|
|
|
|
}
|
|
|
|
|
2020-06-07 16:53:37 +00:00
|
|
|
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateSoftwareCursorDrawRect() const
|
2020-06-30 14:43:17 +00:00
|
|
|
{
|
|
|
|
return CalculateSoftwareCursorDrawRect(m_mouse_position_x, m_mouse_position_y);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const
|
2020-06-07 16:53:37 +00:00
|
|
|
{
|
2020-06-30 14:35:33 +00:00
|
|
|
const float scale = m_window_info.surface_scale * m_cursor_texture_scale;
|
|
|
|
const u32 cursor_extents_x = static_cast<u32>(static_cast<float>(m_cursor_texture->GetWidth()) * scale * 0.5f);
|
|
|
|
const u32 cursor_extents_y = static_cast<u32>(static_cast<float>(m_cursor_texture->GetHeight()) * scale * 0.5f);
|
2020-06-07 16:53:37 +00:00
|
|
|
|
2020-06-30 14:43:17 +00:00
|
|
|
const s32 out_left = cursor_x - cursor_extents_x;
|
|
|
|
const s32 out_top = cursor_y - cursor_extents_y;
|
2020-06-07 16:53:37 +00:00
|
|
|
const s32 out_width = cursor_extents_x * 2u;
|
|
|
|
const s32 out_height = cursor_extents_y * 2u;
|
|
|
|
|
|
|
|
return std::tie(out_left, out_top, out_width, out_height);
|
|
|
|
}
|
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
std::tuple<s32, s32> HostDisplay::ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y,
|
|
|
|
s32 window_width, s32 window_height,
|
|
|
|
s32 top_margin) const
|
|
|
|
{
|
|
|
|
s32 left, top, width, height, left_padding, top_padding;
|
|
|
|
float scale, y_scale;
|
|
|
|
CalculateDrawRect(window_width, window_height - top_margin, &left, &top, &width, &height, &left_padding, &top_padding,
|
|
|
|
&scale, &y_scale);
|
|
|
|
|
|
|
|
// convert coordinates to active display region, then to full display region
|
|
|
|
const float scaled_display_x = static_cast<float>(window_x - (left_padding));
|
|
|
|
const float scaled_display_y = static_cast<float>(window_y - (top_padding + top_margin));
|
2020-03-28 15:14:37 +00:00
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
// scale back to internal resolution
|
|
|
|
const float display_x = scaled_display_x / scale;
|
|
|
|
const float display_y = scaled_display_y / scale / y_scale;
|
2020-02-28 07:01:01 +00:00
|
|
|
|
2020-04-25 15:10:46 +00:00
|
|
|
return std::make_tuple(static_cast<s32>(display_x), static_cast<s32>(display_y));
|
2020-01-24 04:51:36 +00:00
|
|
|
}
|
2020-03-15 14:03:49 +00:00
|
|
|
|
2020-11-26 12:40:58 +00:00
|
|
|
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
|
|
|
|
bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
|
|
|
|
std::vector<u32> texture_data, u32 texture_data_stride)
|
2020-03-15 14:03:49 +00:00
|
|
|
{
|
|
|
|
|
2020-11-26 12:40:58 +00:00
|
|
|
const char* extension = std::strrchr(filename.c_str(), '.');
|
2020-03-15 14:03:49 +00:00
|
|
|
if (!extension)
|
|
|
|
{
|
2020-11-26 12:40:58 +00:00
|
|
|
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename.c_str());
|
2020-03-15 14:03:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (clear_alpha)
|
|
|
|
{
|
|
|
|
for (u32& pixel : texture_data)
|
|
|
|
pixel |= 0xFF000000;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flip_y)
|
|
|
|
{
|
|
|
|
std::vector<u32> temp(width);
|
|
|
|
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
|
|
|
|
{
|
|
|
|
u32* top_ptr = &texture_data[flip_row * width];
|
|
|
|
u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width];
|
|
|
|
std::memcpy(temp.data(), top_ptr, texture_data_stride);
|
|
|
|
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
|
|
|
|
std::memcpy(bottom_ptr, temp.data(), texture_data_stride);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
|
|
|
{
|
|
|
|
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
|
|
|
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
|
|
|
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
|
|
|
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
|
|
|
resized_texture_stride, 4))
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
width = resize_width;
|
|
|
|
height = resize_height;
|
|
|
|
texture_data = std::move(resized_texture_data);
|
|
|
|
texture_data_stride = resized_texture_stride;
|
|
|
|
}
|
|
|
|
|
2020-09-14 19:27:22 +00:00
|
|
|
const auto write_func = [](void* context, void* data, int size) {
|
|
|
|
std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
|
|
|
|
};
|
|
|
|
|
|
|
|
bool result = false;
|
2020-03-15 14:03:49 +00:00
|
|
|
if (StringUtil::Strcasecmp(extension, ".png") == 0)
|
|
|
|
{
|
2020-09-14 19:27:22 +00:00
|
|
|
result =
|
|
|
|
(stbi_write_png_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), texture_data_stride) != 0);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
2020-11-26 12:40:58 +00:00
|
|
|
else if (StringUtil::Strcasecmp(filename.c_str(), ".jpg") == 0)
|
2020-03-15 14:03:49 +00:00
|
|
|
{
|
2020-09-14 19:27:22 +00:00
|
|
|
result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), 95) != 0);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
2020-11-26 12:40:58 +00:00
|
|
|
else if (StringUtil::Strcasecmp(filename.c_str(), ".tga") == 0)
|
2020-03-15 14:03:49 +00:00
|
|
|
{
|
2020-09-14 19:27:22 +00:00
|
|
|
result = (stbi_write_tga_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
2020-11-26 12:40:58 +00:00
|
|
|
else if (StringUtil::Strcasecmp(filename.c_str(), ".bmp") == 0)
|
2020-03-15 14:03:49 +00:00
|
|
|
{
|
2020-09-14 19:27:22 +00:00
|
|
|
result = (stbi_write_bmp_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!result)
|
|
|
|
{
|
2020-11-26 12:40:58 +00:00
|
|
|
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HostDisplay::WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u32 width, u32 height,
|
2020-11-29 05:10:48 +00:00
|
|
|
std::string filename, bool clear_alpha /* = true */, bool flip_y /* = false */,
|
2020-11-26 12:40:58 +00:00
|
|
|
u32 resize_width /* = 0 */, u32 resize_height /* = 0 */,
|
|
|
|
bool compress_on_thread /* = false */)
|
|
|
|
{
|
|
|
|
std::vector<u32> texture_data(width * height);
|
|
|
|
u32 texture_data_stride = sizeof(u32) * width;
|
|
|
|
if (!DownloadTexture(texture_handle, x, y, width, height, texture_data.data(), texture_data_stride))
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Texture download failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-11-29 05:10:48 +00:00
|
|
|
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
|
2020-11-26 12:40:58 +00:00
|
|
|
if (!fp)
|
|
|
|
{
|
2020-11-29 05:10:48 +00:00
|
|
|
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
|
2020-03-15 14:03:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-11-26 12:40:58 +00:00
|
|
|
if (!compress_on_thread)
|
|
|
|
{
|
2020-11-29 05:10:48 +00:00
|
|
|
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), clear_alpha, flip_y,
|
|
|
|
resize_width, resize_height, std::move(texture_data), texture_data_stride);
|
2020-11-26 12:40:58 +00:00
|
|
|
}
|
|
|
|
|
2020-11-29 05:10:48 +00:00
|
|
|
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp),
|
|
|
|
clear_alpha, flip_y, resize_width, resize_height, std::move(texture_data),
|
|
|
|
texture_data_stride);
|
2020-11-26 12:40:58 +00:00
|
|
|
compress_thread.detach();
|
2020-03-15 14:03:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-11-29 05:10:48 +00:00
|
|
|
bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_resolution /* = true */,
|
2020-11-26 12:40:58 +00:00
|
|
|
bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = false */)
|
2020-03-15 14:03:49 +00:00
|
|
|
{
|
2020-10-21 15:25:33 +00:00
|
|
|
if (!m_display_texture_handle || m_display_texture_format != HostDisplayPixelFormat::RGBA8)
|
2020-03-15 14:03:49 +00:00
|
|
|
return false;
|
|
|
|
|
2020-07-13 00:07:01 +00:00
|
|
|
apply_aspect_ratio = (m_display_aspect_ratio > 0) ? apply_aspect_ratio : false;
|
|
|
|
|
2020-03-15 14:03:49 +00:00
|
|
|
s32 resize_width = 0;
|
|
|
|
s32 resize_height = 0;
|
|
|
|
if (apply_aspect_ratio && full_resolution)
|
|
|
|
{
|
2020-06-29 16:46:57 +00:00
|
|
|
if (m_display_aspect_ratio > 1.0f)
|
2020-03-15 14:03:49 +00:00
|
|
|
{
|
|
|
|
resize_width = m_display_texture_view_width;
|
2020-06-29 16:46:57 +00:00
|
|
|
resize_height = static_cast<s32>(static_cast<float>(resize_width) / m_display_aspect_ratio);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-10 14:56:28 +00:00
|
|
|
resize_height = std::abs(m_display_texture_view_height);
|
2020-06-29 16:46:57 +00:00
|
|
|
resize_width = static_cast<s32>(static_cast<float>(resize_height) * m_display_aspect_ratio);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (apply_aspect_ratio)
|
|
|
|
{
|
2020-06-29 16:46:57 +00:00
|
|
|
const auto [left, top, right, bottom] =
|
|
|
|
CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin);
|
2020-03-15 14:03:49 +00:00
|
|
|
resize_width = right - left;
|
|
|
|
resize_height = bottom - top;
|
|
|
|
}
|
|
|
|
else if (!full_resolution)
|
|
|
|
{
|
2020-06-29 16:46:57 +00:00
|
|
|
const auto [left, top, right, bottom] =
|
|
|
|
CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin);
|
2020-03-15 14:03:49 +00:00
|
|
|
const float ratio =
|
2020-04-10 14:56:28 +00:00
|
|
|
static_cast<float>(m_display_texture_view_width) / static_cast<float>(std::abs(m_display_texture_view_height));
|
2020-03-15 14:03:49 +00:00
|
|
|
if (ratio > 1.0f)
|
|
|
|
{
|
|
|
|
resize_width = right - left;
|
|
|
|
resize_height = static_cast<s32>(static_cast<float>(resize_width) / ratio);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
resize_height = bottom - top;
|
|
|
|
resize_width = static_cast<s32>(static_cast<float>(resize_height) * ratio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resize_width < 0)
|
|
|
|
resize_width = 1;
|
|
|
|
if (resize_height < 0)
|
|
|
|
resize_height = 1;
|
|
|
|
|
|
|
|
const bool flip_y = (m_display_texture_view_height < 0);
|
|
|
|
s32 read_height = m_display_texture_view_height;
|
|
|
|
s32 read_y = m_display_texture_view_y;
|
|
|
|
if (flip_y)
|
|
|
|
{
|
|
|
|
read_height = -m_display_texture_view_height;
|
|
|
|
read_y = (m_display_texture_height - read_height) - (m_display_texture_height - m_display_texture_view_y);
|
|
|
|
}
|
|
|
|
|
|
|
|
return WriteTextureToFile(m_display_texture_handle, m_display_texture_view_x, read_y, m_display_texture_view_width,
|
2020-11-29 05:10:48 +00:00
|
|
|
read_height, std::move(filename), true, flip_y, static_cast<u32>(resize_width),
|
2020-11-26 12:40:58 +00:00
|
|
|
static_cast<u32>(resize_height), compress_on_thread);
|
2020-03-15 14:03:49 +00:00
|
|
|
}
|
2020-04-20 16:50:45 +00:00
|
|
|
|
|
|
|
bool HostDisplay::WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width /* = 0 */,
|
|
|
|
u32 resize_height /* = 0 */, bool clear_alpha /* = true */)
|
|
|
|
{
|
2020-10-21 15:25:33 +00:00
|
|
|
if (!m_display_texture_handle || m_display_texture_format != HostDisplayPixelFormat::RGBA8)
|
2020-04-20 16:50:45 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
const bool flip_y = (m_display_texture_view_height < 0);
|
|
|
|
s32 read_width = m_display_texture_view_width;
|
|
|
|
s32 read_height = m_display_texture_view_height;
|
|
|
|
s32 read_x = m_display_texture_view_x;
|
|
|
|
s32 read_y = m_display_texture_view_y;
|
|
|
|
if (flip_y)
|
|
|
|
{
|
|
|
|
read_height = -m_display_texture_view_height;
|
|
|
|
read_y = (m_display_texture_height - read_height) - (m_display_texture_height - m_display_texture_view_y);
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 width = static_cast<u32>(read_width);
|
|
|
|
u32 height = static_cast<u32>(read_height);
|
|
|
|
std::vector<u32> texture_data(width * height);
|
|
|
|
u32 texture_data_stride = sizeof(u32) * width;
|
|
|
|
if (!DownloadTexture(m_display_texture_handle, read_x, read_y, width, height, texture_data.data(),
|
|
|
|
texture_data_stride))
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to download texture from GPU.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (clear_alpha)
|
|
|
|
{
|
|
|
|
for (u32& pixel : texture_data)
|
|
|
|
pixel |= 0xFF000000;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flip_y)
|
|
|
|
{
|
|
|
|
std::vector<u32> temp(width);
|
|
|
|
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
|
|
|
|
{
|
|
|
|
u32* top_ptr = &texture_data[flip_row * width];
|
|
|
|
u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width];
|
|
|
|
std::memcpy(temp.data(), top_ptr, texture_data_stride);
|
|
|
|
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
|
|
|
|
std::memcpy(bottom_ptr, temp.data(), texture_data_stride);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
|
|
|
{
|
|
|
|
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
|
|
|
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
|
|
|
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
|
|
|
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
|
|
|
resized_texture_stride, 4))
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
width = resize_width;
|
|
|
|
height = resize_height;
|
|
|
|
*buffer = std::move(resized_texture_data);
|
|
|
|
texture_data_stride = resized_texture_stride;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*buffer = texture_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|