Fix SIMD bug by Gemini
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
#include "pch.h"
|
||||
#include "pch.h"
|
||||
#include "FileOutput.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
namespace Vav2Player {
|
||||
|
||||
// 📈 정적 룩업 테이블 정의
|
||||
int FileOutput::s_yuv_table_r[256];
|
||||
int FileOutput::s_yuv_table_g_u[256];
|
||||
int FileOutput::s_yuv_table_g_v[256];
|
||||
@@ -22,7 +21,6 @@ FileOutput::FileOutput(const OutputConfig& config)
|
||||
: m_config(config) {
|
||||
m_stats.start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// SIMD 지원 여부 확인 및 로깅
|
||||
bool simd_supported = IsSIMDSupported();
|
||||
OutputDebugStringA(simd_supported ?
|
||||
"[FileOutput] CPU SIMD AVX2 support: ENABLED\n" :
|
||||
@@ -33,14 +31,12 @@ FileOutput::FileOutput(const OutputConfig& config)
|
||||
OutputDebugStringA("[FileOutput] YUV->RGB conversion will use lookup table optimization\n");
|
||||
}
|
||||
|
||||
// 성능 최적화를 위한 캐시 초기화
|
||||
InitializeCache();
|
||||
}
|
||||
|
||||
void FileOutput::SetConfig(const OutputConfig& config) {
|
||||
m_config = config;
|
||||
|
||||
// 설정 변경 시 캐시 다시 초기화
|
||||
m_directory_initialized = false;
|
||||
InitializeCache();
|
||||
}
|
||||
@@ -52,20 +48,15 @@ void FileOutput::SetProgressCallback(ProgressCallback callback) {
|
||||
FileOutput::SaveResult FileOutput::SaveFrame(const VideoFrame& frame, uint64_t frame_index, double timestamp) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// 📈 최적화: 디렉토리는 처음 한 번만 생성
|
||||
if (!m_directory_initialized) {
|
||||
std::cout << "[FileOutput Debug] Attempting to create directory: " << m_config.output_directory.string() << std::endl;
|
||||
if (!CreateOutputDirectory()) {
|
||||
return CreateErrorResult("Failed to create output directory");
|
||||
}
|
||||
m_directory_initialized = true;
|
||||
std::cout << "[FileOutput Debug] Directory creation successful" << std::endl;
|
||||
}
|
||||
|
||||
// 📈 최적화: 파일명을 빠르게 생성 (문자열 재할당 최소화)
|
||||
auto file_path = GenerateOptimizedFilename(frame_index);
|
||||
|
||||
// 기존 파일 덮어쓰기 확인
|
||||
try {
|
||||
if (!m_config.overwrite_existing && std::filesystem::exists(file_path)) {
|
||||
return CreateErrorResult("File already exists and overwrite is disabled");
|
||||
@@ -76,7 +67,6 @@ FileOutput::SaveResult FileOutput::SaveFrame(const VideoFrame& frame, uint64_t f
|
||||
|
||||
SaveResult result;
|
||||
|
||||
// 포맷별 저장
|
||||
switch (m_config.format) {
|
||||
case OutputFormat::RawYUV:
|
||||
result = SaveAsRawYUV(frame, file_path);
|
||||
@@ -92,12 +82,18 @@ FileOutput::SaveResult FileOutput::SaveFrame(const VideoFrame& frame, uint64_t f
|
||||
break;
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration_ms = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
UpdateSaveStats(result.success, result.file_size_bytes, duration_ms);
|
||||
|
||||
// 진행 상황 콜백 호출
|
||||
if (result.success) {
|
||||
char buffer[256];
|
||||
sprintf_s(buffer, sizeof(buffer), "[FileOutput] Saved frame %llu. Total time: %.2f ms, Conversion time: %.2f ms\n",
|
||||
frame_index, duration_ms, result.conversion_time_ms);
|
||||
OutputDebugStringA(buffer);
|
||||
std::cout << buffer;
|
||||
}
|
||||
|
||||
if (result.success && m_progress_callback) {
|
||||
m_progress_callback(frame_index, result.saved_path);
|
||||
}
|
||||
@@ -112,20 +108,15 @@ bool FileOutput::SaveFrameSequence(const VideoFrame& frame, uint64_t frame_index
|
||||
|
||||
bool FileOutput::CreateOutputDirectory() {
|
||||
try {
|
||||
// 현재 프로세스 경로를 기반으로 출력 디렉토리 경로 생성
|
||||
std::filesystem::path exe_path = GetProcessPath();
|
||||
std::filesystem::path full_output_path = exe_path / m_config.output_directory;
|
||||
|
||||
std::cout << "[FileOutput Debug] Process path: " << exe_path.string() << std::endl;
|
||||
std::cout << "[FileOutput Debug] Final output directory: " << full_output_path.string() << std::endl;
|
||||
|
||||
if (!std::filesystem::exists(full_output_path)) {
|
||||
return std::filesystem::create_directories(full_output_path);
|
||||
}
|
||||
return true;
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
std::cout << "[FileOutput Error] Failed to create directory: " << e.what() << std::endl;
|
||||
std::cout << "[FileOutput Error] Directory path: " << m_config.output_directory.string() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -140,7 +131,6 @@ void FileOutput::ClearOutputDirectory() {
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
std::cout << "[FileOutput Error] Failed to clear directory: " << e.what() << std::endl;
|
||||
std::cout << "[FileOutput Error] Directory path: " << (GetProcessPath() / m_config.output_directory).string() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +168,6 @@ std::filesystem::path FileOutput::GenerateFilename(const std::string& prefix,
|
||||
return std::filesystem::path(prefix + "_" + std::to_string(frame_index) + extension);
|
||||
}
|
||||
|
||||
// Raw YUV 파일 저장
|
||||
FileOutput::SaveResult FileOutput::SaveAsRawYUV(const VideoFrame& frame, const std::filesystem::path& file_path) {
|
||||
try {
|
||||
std::ofstream file(file_path, std::ios::binary);
|
||||
@@ -188,14 +177,12 @@ FileOutput::SaveResult FileOutput::SaveAsRawYUV(const VideoFrame& frame, const s
|
||||
|
||||
size_t total_bytes = 0;
|
||||
|
||||
// Y 플레인 저장
|
||||
for (uint32_t y = 0; y < frame.height; ++y) {
|
||||
const uint8_t* row_data = frame.y_plane.get() + (y * frame.y_stride);
|
||||
file.write(reinterpret_cast<const char*>(row_data), frame.width);
|
||||
total_bytes += frame.width;
|
||||
}
|
||||
|
||||
// U 플레인 저장
|
||||
uint32_t chroma_width = (frame.color_space == ColorSpace::YUV444P) ? frame.width : frame.width / 2;
|
||||
uint32_t chroma_height = (frame.color_space == ColorSpace::YUV420P) ? frame.height / 2 : frame.height;
|
||||
|
||||
@@ -205,7 +192,6 @@ FileOutput::SaveResult FileOutput::SaveAsRawYUV(const VideoFrame& frame, const s
|
||||
total_bytes += chroma_width;
|
||||
}
|
||||
|
||||
// V 플레인 저장
|
||||
for (uint32_t y = 0; y < chroma_height; ++y) {
|
||||
const uint8_t* row_data = frame.v_plane.get() + (y * frame.v_stride);
|
||||
file.write(reinterpret_cast<const char*>(row_data), chroma_width);
|
||||
@@ -223,30 +209,25 @@ FileOutput::SaveResult FileOutput::SaveAsRawYUV(const VideoFrame& frame, const s
|
||||
}
|
||||
}
|
||||
|
||||
// BMP 파일 저장 (기존 방식 - 호환성용)
|
||||
FileOutput::SaveResult FileOutput::SaveAsBMP(const VideoFrame& frame, const std::filesystem::path& file_path) {
|
||||
// 📈 성능 최적화: 메모리 매핑 방식 사용
|
||||
return SaveAsBMP_MemoryMapped(frame, file_path);
|
||||
}
|
||||
|
||||
// 📈 메모리 매핑 기반 BMP 파일 저장 (고성능)
|
||||
FileOutput::SaveResult FileOutput::SaveAsBMP_MemoryMapped(const VideoFrame& frame, const std::filesystem::path& file_path) {
|
||||
// YUV → RGB 변환
|
||||
auto conversion_start_time = std::chrono::high_resolution_clock::now();
|
||||
RGBFrame rgb_frame;
|
||||
if (!ConvertYUVToRGB(frame, rgb_frame)) {
|
||||
return CreateErrorResult("Failed to convert YUV to RGB");
|
||||
}
|
||||
|
||||
try {
|
||||
// BMP 헤더 생성
|
||||
auto header = CreateBMPHeader(rgb_frame.width, rgb_frame.height);
|
||||
uint32_t row_size = rgb_frame.width * 3;
|
||||
uint32_t padding = (4 - (row_size % 4)) % 4;
|
||||
uint32_t padded_row_size = row_size + padding;
|
||||
uint32_t header_size = 54; // BMP 헤더 크기
|
||||
uint32_t header_size = 54;
|
||||
uint32_t total_file_size = header_size + (padded_row_size * rgb_frame.height);
|
||||
|
||||
// 📈 Windows 메모리 매핑 파일 생성
|
||||
HANDLE hFile = CreateFileW(
|
||||
file_path.wstring().c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
@@ -289,10 +270,8 @@ FileOutput::SaveResult FileOutput::SaveAsBMP_MemoryMapped(const VideoFrame& fram
|
||||
return CreateErrorResult("Failed to map view of file: " + std::to_string(GetLastError()));
|
||||
}
|
||||
|
||||
// 📈 메모리에 직접 헤더 쓰기 (파일 I/O 없음)
|
||||
uint8_t* write_ptr = mapped_memory;
|
||||
|
||||
// BMP 헤더 복사
|
||||
memcpy(write_ptr, &header.file_type, sizeof(header.file_type)); write_ptr += sizeof(header.file_type);
|
||||
memcpy(write_ptr, &header.file_size, sizeof(header.file_size)); write_ptr += sizeof(header.file_size);
|
||||
memcpy(write_ptr, &header.reserved1, sizeof(header.reserved1)); write_ptr += sizeof(header.reserved1);
|
||||
@@ -310,24 +289,25 @@ FileOutput::SaveResult FileOutput::SaveAsBMP_MemoryMapped(const VideoFrame& fram
|
||||
memcpy(write_ptr, &header.colors_used, sizeof(header.colors_used)); write_ptr += sizeof(header.colors_used);
|
||||
memcpy(write_ptr, &header.colors_important, sizeof(header.colors_important));
|
||||
|
||||
// 📈 RGB→BGR 변환하면서 메모리에 직접 쓰기 (BMP는 bottom-up)
|
||||
for (int32_t y = static_cast<int32_t>(rgb_frame.height) - 1; y >= 0; --y) {
|
||||
uint8_t* dst_row = mapped_memory + header_size + ((rgb_frame.height - 1 - y) * padded_row_size);
|
||||
const uint8_t* src_row = rgb_frame.data.data() + (y * rgb_frame.stride);
|
||||
|
||||
// 라인 단위 RGB→BGR 변환 및 패딩 처리
|
||||
ConvertRGBToBGRLine(src_row, dst_row, rgb_frame.width, padding);
|
||||
}
|
||||
|
||||
// 메모리 매핑 해제
|
||||
UnmapViewOfFile(mapped_memory);
|
||||
CloseHandle(hMapping);
|
||||
CloseHandle(hFile);
|
||||
|
||||
auto conversion_end_time = std::chrono::high_resolution_clock::now();
|
||||
double conversion_time_ms = std::chrono::duration<double, std::milli>(conversion_end_time - conversion_start_time).count();
|
||||
|
||||
SaveResult result;
|
||||
result.success = true;
|
||||
result.saved_path = file_path;
|
||||
result.file_size_bytes = total_file_size;
|
||||
result.conversion_time_ms = conversion_time_ms;
|
||||
return result;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
@@ -335,11 +315,10 @@ FileOutput::SaveResult FileOutput::SaveAsBMP_MemoryMapped(const VideoFrame& fram
|
||||
}
|
||||
}
|
||||
|
||||
// YUV → RGB 변환
|
||||
bool FileOutput::ConvertYUVToRGB(const VideoFrame& yuv_frame, RGBFrame& rgb_frame) {
|
||||
rgb_frame.width = yuv_frame.width;
|
||||
rgb_frame.height = yuv_frame.height;
|
||||
rgb_frame.stride = yuv_frame.width * 3; // RGB24
|
||||
rgb_frame.stride = yuv_frame.width * 3;
|
||||
rgb_frame.data.resize(rgb_frame.height * rgb_frame.stride);
|
||||
|
||||
switch (yuv_frame.color_space) {
|
||||
@@ -368,34 +347,31 @@ bool FileOutput::ConvertYUVToRGB(const VideoFrame& yuv_frame, RGBFrame& rgb_fram
|
||||
return true;
|
||||
}
|
||||
|
||||
// YUV420P → RGB24 변환 (최적화된 버전 - SIMD 지원 여부에 따라 선택)
|
||||
void FileOutput::ConvertYUV420PToRGB24(const uint8_t* y_plane, const uint8_t* u_plane, const uint8_t* v_plane,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t y_stride, uint32_t u_stride, uint32_t v_stride,
|
||||
uint8_t* rgb_data, uint32_t rgb_stride) {
|
||||
// 📈 SIMD 지원 시 AVX2 사용, 아니면 룩업 테이블 사용
|
||||
if (IsSIMDSupported()) {
|
||||
ConvertYUV420PToRGB24_SIMD(y_plane, u_plane, v_plane, width, height,
|
||||
ConvertYUV420PToRGB24_SIMD(y_plane, u_plane, v_plane,
|
||||
width, height,
|
||||
y_stride, u_stride, v_stride, rgb_data, rgb_stride);
|
||||
} else {
|
||||
ConvertYUV420PToRGB24_Optimized(y_plane, u_plane, v_plane, width, height,
|
||||
ConvertYUV420PToRGB24_Optimized(y_plane, u_plane, v_plane,
|
||||
width, height,
|
||||
y_stride, u_stride, v_stride, rgb_data, rgb_stride);
|
||||
}
|
||||
}
|
||||
|
||||
// YUV422P → RGB24 변환 (4:2:2 서브샘플링)
|
||||
void FileOutput::ConvertYUV422PToRGB24(const uint8_t* y_plane, const uint8_t* u_plane, const uint8_t* v_plane,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t y_stride, uint32_t u_stride, uint32_t v_stride,
|
||||
uint8_t* rgb_data, uint32_t rgb_stride) {
|
||||
for (uint32_t y = 0; y < height; ++y) {
|
||||
for (uint32_t x = 0; x < width; ++x) {
|
||||
// YUV 값 가져오기
|
||||
uint8_t Y = y_plane[y * y_stride + x];
|
||||
uint8_t U = u_plane[y * u_stride + (x / 2)]; // 수평만 서브샘플링
|
||||
uint8_t U = u_plane[y * u_stride + (x / 2)];
|
||||
uint8_t V = v_plane[y * v_stride + (x / 2)];
|
||||
|
||||
// YUV → RGB 변환
|
||||
int C = Y - 16;
|
||||
int D = U - 128;
|
||||
int E = V - 128;
|
||||
@@ -416,19 +392,16 @@ void FileOutput::ConvertYUV422PToRGB24(const uint8_t* y_plane, const uint8_t* u_
|
||||
}
|
||||
}
|
||||
|
||||
// YUV444P → RGB24 변환 (4:4:4 풀 해상도)
|
||||
void FileOutput::ConvertYUV444PToRGB24(const uint8_t* y_plane, const uint8_t* u_plane, const uint8_t* v_plane,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t y_stride, uint32_t u_stride, uint32_t v_stride,
|
||||
uint8_t* rgb_data, uint32_t rgb_stride) {
|
||||
for (uint32_t y = 0; y < height; ++y) {
|
||||
for (uint32_t x = 0; x < width; ++x) {
|
||||
// YUV 값 가져오기
|
||||
uint8_t Y = y_plane[y * y_stride + x];
|
||||
uint8_t U = u_plane[y * u_stride + x]; // 서브샘플링 없음
|
||||
uint8_t U = u_plane[y * u_stride + x];
|
||||
uint8_t V = v_plane[y * v_stride + x];
|
||||
|
||||
// YUV → RGB 변환
|
||||
int C = Y - 16;
|
||||
int D = U - 128;
|
||||
int E = V - 128;
|
||||
@@ -449,7 +422,6 @@ void FileOutput::ConvertYUV444PToRGB24(const uint8_t* y_plane, const uint8_t* u_
|
||||
}
|
||||
}
|
||||
|
||||
// BMP 헤더 생성
|
||||
FileOutput::BMPHeader FileOutput::CreateBMPHeader(uint32_t width, uint32_t height) {
|
||||
BMPHeader header;
|
||||
|
||||
@@ -461,12 +433,11 @@ FileOutput::BMPHeader FileOutput::CreateBMPHeader(uint32_t width, uint32_t heigh
|
||||
header.width = static_cast<int32_t>(width);
|
||||
header.height = static_cast<int32_t>(height);
|
||||
header.size_image = image_size;
|
||||
header.file_size = 54 + image_size; // 헤더 크기 + 이미지 크기
|
||||
header.file_size = 54 + image_size;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
// 파일 쓰기 유틸리티
|
||||
bool FileOutput::WriteDataToFile(const std::filesystem::path& file_path,
|
||||
const void* data, size_t size) {
|
||||
try {
|
||||
@@ -483,7 +454,6 @@ bool FileOutput::WriteDataToFile(const std::filesystem::path& file_path,
|
||||
}
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
void FileOutput::UpdateSaveStats(bool success, size_t bytes_written, double save_time_ms) {
|
||||
std::lock_guard<std::mutex> lock(m_stats_mutex);
|
||||
|
||||
@@ -491,7 +461,6 @@ void FileOutput::UpdateSaveStats(bool success, size_t bytes_written, double save
|
||||
m_stats.frames_saved++;
|
||||
m_stats.total_bytes_written += bytes_written;
|
||||
|
||||
// 평균 저장 시간 업데이트 (지수 이동 평균)
|
||||
const double alpha = 0.1;
|
||||
m_stats.avg_save_time_ms = alpha * save_time_ms + (1.0 - alpha) * m_stats.avg_save_time_ms;
|
||||
} else {
|
||||
@@ -499,7 +468,6 @@ void FileOutput::UpdateSaveStats(bool success, size_t bytes_written, double save
|
||||
}
|
||||
}
|
||||
|
||||
// 에러 결과 생성
|
||||
FileOutput::SaveResult FileOutput::CreateErrorResult(const std::string& error_message) {
|
||||
SaveResult result;
|
||||
result.success = false;
|
||||
@@ -508,27 +476,20 @@ FileOutput::SaveResult FileOutput::CreateErrorResult(const std::string& error_me
|
||||
return result;
|
||||
}
|
||||
|
||||
// 현재 프로세스 경로 가져오기
|
||||
std::filesystem::path FileOutput::GetProcessPath() const {
|
||||
// Windows에서 현재 실행 파일의 경로 가져오기
|
||||
wchar_t exe_full_path[MAX_PATH];
|
||||
DWORD path_length = GetModuleFileNameW(nullptr, exe_full_path, MAX_PATH);
|
||||
|
||||
if (path_length == 0 || path_length == MAX_PATH) {
|
||||
// GetModuleFileName 실패 시 현재 작업 디렉토리 사용
|
||||
std::cout << "[FileOutput Warning] Failed to get executable path, using current working directory" << std::endl;
|
||||
return std::filesystem::current_path();
|
||||
} else {
|
||||
return std::filesystem::path(exe_full_path).parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
// 📈 성능 최적화: 캐시 초기화
|
||||
void FileOutput::InitializeCache() {
|
||||
// 전체 출력 경로 캐시 (프로세스 경로 + 출력 디렉토리)
|
||||
m_cached_output_path = GetProcessPath() / m_config.output_directory;
|
||||
|
||||
// 파일 확장자 캐시
|
||||
switch (m_config.format) {
|
||||
case OutputFormat::RawYUV: m_cached_file_extension = ".yuv"; break;
|
||||
case OutputFormat::BMP: m_cached_file_extension = ".bmp"; break;
|
||||
@@ -536,102 +497,79 @@ void FileOutput::InitializeCache() {
|
||||
default: m_cached_file_extension = ".dat"; break;
|
||||
}
|
||||
|
||||
// 파일명 버퍼 미리 예약 (메모리 재할당 최소화)
|
||||
// 예상 최대 길이: prefix + "_" + frame_number + extension
|
||||
// 가정: prefix=20자, frame_number=10자, extension=4자, 여유분=50자
|
||||
m_filename_buffer.reserve(100);
|
||||
}
|
||||
|
||||
// 📈 성능 최적화: 파일명 빠른 생성
|
||||
std::filesystem::path FileOutput::GenerateOptimizedFilename(uint64_t frame_index) {
|
||||
// 기존 버퍼 내용 지우기 (메모리 재할당 없음)
|
||||
m_filename_buffer.clear();
|
||||
|
||||
// 효율적인 문자열 생성 (append 기반, 재할당 최소화)
|
||||
m_filename_buffer.append(m_config.filename_prefix);
|
||||
m_filename_buffer.append("_");
|
||||
m_filename_buffer.append(std::to_string(frame_index));
|
||||
m_filename_buffer.append(m_cached_file_extension);
|
||||
|
||||
// 캐시된 전체 경로와 결합
|
||||
return m_cached_output_path / m_filename_buffer;
|
||||
}
|
||||
|
||||
// 📈 RGB→BGR 라인 변환 (메모리 매핑 최적화용)
|
||||
void FileOutput::ConvertRGBToBGRLine(const uint8_t* src_rgb, uint8_t* dst_bgr, uint32_t width, uint32_t padding) {
|
||||
// RGB → BGR 변환 (3픽셀씩 처리로 최적화)
|
||||
for (uint32_t x = 0; x < width; ++x) {
|
||||
dst_bgr[x * 3 + 0] = src_rgb[x * 3 + 2]; // B = R
|
||||
dst_bgr[x * 3 + 1] = src_rgb[x * 3 + 1]; // G = G
|
||||
dst_bgr[x * 3 + 2] = src_rgb[x * 3 + 0]; // R = B
|
||||
dst_bgr[x * 3 + 0] = src_rgb[x * 3 + 2];
|
||||
dst_bgr[x * 3 + 1] = src_rgb[x * 3 + 1];
|
||||
dst_bgr[x * 3 + 2] = src_rgb[x * 3 + 0];
|
||||
}
|
||||
|
||||
// 패딩 영역을 0으로 채움 (4바이트 정렬)
|
||||
if (padding > 0) {
|
||||
memset(dst_bgr + width * 3, 0, padding);
|
||||
}
|
||||
}
|
||||
|
||||
// 📈 YUV→RGB 룩업 테이블 초기화 (프로그램 시작 시 한 번만 실행)
|
||||
void FileOutput::InitializeYUVTables() {
|
||||
if (s_tables_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// BT.601 표준 YUV→RGB 변환 계수를 룩업 테이블로 미리 계산
|
||||
// Store only the multiplication result, not the shifted one, to match SIMD logic.
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
// V → R 변환: 409 * (V - 128) / 256
|
||||
s_yuv_table_r[i] = (409 * (i - 128)) >> 8;
|
||||
|
||||
// U → G 변환: -100 * (U - 128) / 256
|
||||
s_yuv_table_g_u[i] = (-100 * (i - 128)) >> 8;
|
||||
|
||||
// V → G 변환: -208 * (V - 128) / 256
|
||||
s_yuv_table_g_v[i] = (-208 * (i - 128)) >> 8;
|
||||
|
||||
// U → B 변환: 516 * (U - 128) / 256
|
||||
s_yuv_table_b[i] = (516 * (i - 128)) >> 8;
|
||||
s_yuv_table_r[i] = 409 * (i - 128);
|
||||
s_yuv_table_g_u[i] = -100 * (i - 128);
|
||||
s_yuv_table_g_v[i] = -208 * (i - 128);
|
||||
s_yuv_table_b[i] = 516 * (i - 128);
|
||||
}
|
||||
|
||||
s_tables_initialized = true;
|
||||
}
|
||||
|
||||
// 📈 최적화된 YUV420P → RGB24 변환 (룩업 테이블 + 라인 처리)
|
||||
// Optimized YUV420P -> RGB24 conversion (lookup table)
|
||||
void FileOutput::ConvertYUV420PToRGB24_Optimized(const uint8_t* y_plane, const uint8_t* u_plane, const uint8_t* v_plane,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t y_stride, uint32_t u_stride, uint32_t v_stride,
|
||||
uint8_t* rgb_data, uint32_t rgb_stride) {
|
||||
// 룩업 테이블 초기화 (첫 번째 호출 시만)
|
||||
InitializeYUVTables();
|
||||
|
||||
// 라인 단위로 처리하여 캐시 효율성 향상
|
||||
for (uint32_t y = 0; y < height; ++y) {
|
||||
const uint8_t* y_row = y_plane + (y * y_stride);
|
||||
const uint8_t* u_row = u_plane + ((y / 2) * u_stride);
|
||||
const uint8_t* v_row = v_plane + ((y / 2) * v_stride);
|
||||
uint8_t* rgb_row = rgb_data + (y * rgb_stride);
|
||||
|
||||
// 2픽셀씩 처리하여 YUV420P 특성 활용 (U, V가 2x2 블록 공유)
|
||||
for (uint32_t x = 0; x < width; x += 2) {
|
||||
uint8_t U = u_row[x / 2];
|
||||
uint8_t V = v_row[x / 2];
|
||||
|
||||
// 📈 룩업 테이블로 변환 계수 조회 (곱셈 연산 제거)
|
||||
// Look up pre-multiplied transform coefficients
|
||||
int r_offset = s_yuv_table_r[V];
|
||||
int g_offset = s_yuv_table_g_u[U] + s_yuv_table_g_v[V];
|
||||
int b_offset = s_yuv_table_b[U];
|
||||
|
||||
// 2픽셀 동시 처리 (U, V 공유로 효율성 증대)
|
||||
for (int px = 0; px < 2 && (x + px) < width; ++px) {
|
||||
int Y = y_row[x + px] - 16;
|
||||
Y = (Y * 298) >> 8; // 📈 비트 시프트로 나눗셈 대체 (더 빠름)
|
||||
int C = y_row[x + px] - 16;
|
||||
int y_base = C * 298;
|
||||
|
||||
// 📈 룩업 테이블 결과와 Y 값 조합
|
||||
int R = Y + r_offset;
|
||||
int G = Y + g_offset;
|
||||
int B = Y + b_offset;
|
||||
// Combine all terms, add rounding factor, and shift at the end
|
||||
int R = (y_base + r_offset + 128) >> 8;
|
||||
int G = (y_base + g_offset + 128) >> 8;
|
||||
int B = (y_base + b_offset + 128) >> 8;
|
||||
|
||||
// 📈 인라인 클램핑 (std::clamp보다 빠름)
|
||||
rgb_row[(x + px) * 3 + 0] = ClampToU8(R);
|
||||
rgb_row[(x + px) * 3 + 1] = ClampToU8(G);
|
||||
rgb_row[(x + px) * 3 + 2] = ClampToU8(B);
|
||||
@@ -640,14 +578,14 @@ void FileOutput::ConvertYUV420PToRGB24_Optimized(const uint8_t* y_plane, const u
|
||||
}
|
||||
}
|
||||
|
||||
// 📈 빠른 클램핑 함수 (0-255 범위로 제한)
|
||||
// Fast clamping function (0-255)
|
||||
int FileOutput::ClampToU8(int value) {
|
||||
if (value < 0) return 0;
|
||||
if (value > 255) return 255;
|
||||
return value;
|
||||
}
|
||||
|
||||
// 📈 SIMD (AVX2) 지원 여부 확인
|
||||
// Check for SIMD (AVX2) support
|
||||
bool FileOutput::IsSIMDSupported() {
|
||||
static bool checked = false;
|
||||
static bool supported = false;
|
||||
@@ -656,7 +594,7 @@ bool FileOutput::IsSIMDSupported() {
|
||||
int cpuInfo[4];
|
||||
__cpuid(cpuInfo, 7);
|
||||
|
||||
// EBX 레지스터의 5번째 비트가 AVX2 지원 여부
|
||||
// Check the 5th bit of the EBX register for AVX2 support
|
||||
supported = (cpuInfo[1] & (1 << 5)) != 0;
|
||||
checked = true;
|
||||
}
|
||||
@@ -664,14 +602,13 @@ bool FileOutput::IsSIMDSupported() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
// 📈 SIMD AVX2 기반 YUV420P → RGB24 변환 (8픽셀 병렬 처리)
|
||||
// SIMD AVX2 based YUV420P -> RGB24 conversion
|
||||
void FileOutput::ConvertYUV420PToRGB24_SIMD(const uint8_t* y_plane, const uint8_t* u_plane, const uint8_t* v_plane,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t y_stride, uint32_t u_stride, uint32_t v_stride,
|
||||
uint8_t* rgb_data, uint32_t rgb_stride) {
|
||||
// AVX2는 8픽셀 단위로 처리, YUV420P 특성상 폭은 2의 배수여야 함
|
||||
const uint32_t simd_width = 8;
|
||||
const uint32_t safe_width = width & ~1; // 홀수 폭 방지 (YUV420P 요구사항)
|
||||
const uint32_t safe_width = width & ~1; // YUV420P requires even width
|
||||
const uint32_t aligned_width = (safe_width / simd_width) * simd_width;
|
||||
|
||||
for (uint32_t y = 0; y < height; ++y) {
|
||||
@@ -680,47 +617,44 @@ void FileOutput::ConvertYUV420PToRGB24_SIMD(const uint8_t* y_plane, const uint8_
|
||||
const uint8_t* v_row = v_plane + ((y / 2) * v_stride);
|
||||
uint8_t* rgb_row = rgb_data + (y * rgb_stride);
|
||||
|
||||
// 📈 8픽셀씩 SIMD 처리 (경계 안전성 보장)
|
||||
for (uint32_t x = 0; x < aligned_width; x += simd_width) {
|
||||
// 경계 검사: U/V 접근이 안전한지 확인
|
||||
uint32_t uv_idx = x / 2;
|
||||
if (uv_idx + 3 >= u_stride) break; // 4바이트 읽기가 안전하지 않으면 중단
|
||||
// Need 4 bytes of U/V data for 8 Y pixels. Break if not available.
|
||||
if (uv_idx + 4 > u_stride || uv_idx + 4 > v_stride) break;
|
||||
|
||||
// Y값 8개 로드 (8bit × 8 = 64bit)
|
||||
// Load 8 Y values
|
||||
__m128i y_128 = _mm_loadl_epi64((__m128i*)(y_row + x));
|
||||
__m256i y_vec = _mm256_cvtepu8_epi16(y_128); // 16bit로 확장
|
||||
__m256i y_vec = _mm256_cvtepu8_epi16(y_128); // extend to 16-bit
|
||||
|
||||
// U, V값 4개씩 안전하게 로드
|
||||
// Safely load 4 U and 4 V values
|
||||
uint32_t u_data = 0, v_data = 0;
|
||||
memcpy(&u_data, u_row + uv_idx, std::min(4u, u_stride - uv_idx));
|
||||
memcpy(&v_data, v_row + uv_idx, std::min(4u, v_stride - uv_idx));
|
||||
memcpy(&u_data, u_row + uv_idx, 4);
|
||||
memcpy(&v_data, v_row + uv_idx, 4);
|
||||
|
||||
// YUV420P 특성에 맞는 U/V 확장: [a,b,c,d] → [a,a,b,b,c,c,d,d]
|
||||
// Expand U/V to match Y: [u0,u1,u2,u3] -> [u0,u0,u1,u1,u2,u2,u3,u3]
|
||||
__m128i u_packed = _mm_cvtsi32_si128(u_data);
|
||||
__m128i v_packed = _mm_cvtsi32_si128(v_data);
|
||||
|
||||
// 정확한 2배 확장: 각 U/V 값이 2개의 Y값에 대응
|
||||
__m128i u_lo = _mm_unpacklo_epi8(u_packed, u_packed); // [a,a,b,b,c,c,d,d,0,0,0,0,0,0,0,0]
|
||||
__m128i u_lo = _mm_unpacklo_epi8(u_packed, u_packed);
|
||||
__m128i v_lo = _mm_unpacklo_epi8(v_packed, v_packed);
|
||||
|
||||
__m256i u_vec = _mm256_cvtepu8_epi16(u_lo);
|
||||
__m256i v_vec = _mm256_cvtepu8_epi16(v_lo);
|
||||
|
||||
// 📈 벡터화된 YUV→RGB 변환
|
||||
// Vectorized YUV->RGB conversion
|
||||
__m256i r_vec, g_vec, b_vec;
|
||||
YUVToRGB_SIMD(y_vec, u_vec, v_vec, r_vec, g_vec, b_vec);
|
||||
|
||||
// 📈 RGB24 형태로 인터리브해서 저장
|
||||
// Interleave and store as RGB24
|
||||
StoreRGB24_SIMD(rgb_row + x * 3, r_vec, g_vec, b_vec, simd_width);
|
||||
}
|
||||
|
||||
// 📈 나머지 픽셀들은 스칼라 처리 (폴백)
|
||||
// Scalar fallback for remaining pixels
|
||||
for (uint32_t x = aligned_width; x < width; ++x) {
|
||||
uint8_t Y = y_row[x];
|
||||
uint8_t U = u_row[x / 2];
|
||||
uint8_t V = v_row[x / 2];
|
||||
|
||||
// 기본 YUV→RGB 변환
|
||||
int C = Y - 16;
|
||||
int D = U - 128;
|
||||
int E = V - 128;
|
||||
@@ -736,87 +670,93 @@ void FileOutput::ConvertYUV420PToRGB24_SIMD(const uint8_t* y_plane, const uint8_
|
||||
}
|
||||
}
|
||||
|
||||
// 📈 SIMD YUV→RGB 변환 연산
|
||||
void FileOutput::YUVToRGB_SIMD(__m256i y_vec, __m256i u_vec, __m256i v_vec,
|
||||
// SIMD YUV->RGB conversion kernel (prevents overflow by using 32-bit intermediates)
|
||||
void FileOutput::YUVToRGB_SIMD(__m256i y_vec, __m256i u_vec, __m256i v_vec,
|
||||
__m256i& r_vec, __m256i& g_vec, __m256i& b_vec) {
|
||||
// 상수 정의 (BT.601 표준)
|
||||
__m256i c16 = _mm256_set1_epi16(16);
|
||||
__m256i c128 = _mm256_set1_epi16(128);
|
||||
__m256i c298 = _mm256_set1_epi16(298);
|
||||
__m256i c409 = _mm256_set1_epi16(409);
|
||||
__m256i c100 = _mm256_set1_epi16(100);
|
||||
__m256i c208 = _mm256_set1_epi16(208);
|
||||
__m256i c516 = _mm256_set1_epi16(516);
|
||||
// Input vectors have 8 valid 16-bit values in their low 128 bits.
|
||||
__m128i y_128 = _mm256_castsi256_si128(y_vec);
|
||||
__m128i u_128 = _mm256_castsi256_si128(u_vec);
|
||||
__m128i v_128 = _mm256_castsi256_si128(v_vec);
|
||||
|
||||
// Y, U, V 값 조정
|
||||
__m256i y_adj = _mm256_sub_epi16(y_vec, c16); // Y - 16
|
||||
__m256i u_adj = _mm256_sub_epi16(u_vec, c128); // U - 128
|
||||
__m256i v_adj = _mm256_sub_epi16(v_vec, c128); // V - 128
|
||||
// Extend all 8 16-bit values to 32-bit.
|
||||
__m256i y_32 = _mm256_cvtepi16_epi32(y_128);
|
||||
__m256i u_32 = _mm256_cvtepi16_epi32(u_128);
|
||||
__m256i v_32 = _mm256_cvtepi16_epi32(v_128);
|
||||
|
||||
// Y 기본값 계산: 298 * (Y - 16)
|
||||
__m256i y_base = _mm256_mullo_epi16(y_adj, c298);
|
||||
// Define 32-bit constants
|
||||
const __m256i c16 = _mm256_set1_epi32(16);
|
||||
const __m256i c128 = _mm256_set1_epi32(128);
|
||||
const __m256i c298 = _mm256_set1_epi32(298);
|
||||
const __m256i c409 = _mm256_set1_epi32(409);
|
||||
const __m256i c100 = _mm256_set1_epi32(100);
|
||||
const __m256i c208 = _mm256_set1_epi32(208);
|
||||
const __m256i c516 = _mm256_set1_epi32(516);
|
||||
const __m256i c128_offset = _mm256_set1_epi32(128);
|
||||
|
||||
// 128 오프셋 상수 추가
|
||||
__m256i c128_offset = _mm256_set1_epi16(128);
|
||||
// --- Process all 8 pixels ---
|
||||
__m256i y_adj = _mm256_sub_epi32(y_32, c16);
|
||||
__m256i u_adj = _mm256_sub_epi32(u_32, c128);
|
||||
__m256i v_adj = _mm256_sub_epi32(v_32, c128);
|
||||
__m256i y_base = _mm256_mullo_epi32(y_adj, c298);
|
||||
|
||||
// R 계산: (Y_base + 409 * (V - 128) + 128) >> 8
|
||||
__m256i r_offset = _mm256_mullo_epi16(v_adj, c409);
|
||||
__m256i r_full = _mm256_add_epi16(y_base, r_offset);
|
||||
r_full = _mm256_add_epi16(r_full, c128_offset); // +128 추가
|
||||
r_vec = _mm256_srai_epi16(r_full, 8); // >> 8
|
||||
__m256i r_full = _mm256_add_epi32(_mm256_add_epi32(y_base, _mm256_mullo_epi32(v_adj, c409)), c128_offset);
|
||||
__m256i g_full = _mm256_add_epi32(_mm256_sub_epi32(_mm256_sub_epi32(y_base, _mm256_mullo_epi32(u_adj, c100)), _mm256_mullo_epi32(v_adj, c208)), c128_offset);
|
||||
__m256i b_full = _mm256_add_epi32(_mm256_add_epi32(y_base, _mm256_mullo_epi32(u_adj, c516)), c128_offset);
|
||||
|
||||
// G 계산: (Y_base - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8
|
||||
__m256i g_offset1 = _mm256_mullo_epi16(u_adj, c100);
|
||||
__m256i g_offset2 = _mm256_mullo_epi16(v_adj, c208);
|
||||
__m256i g_full = _mm256_sub_epi16(y_base, g_offset1);
|
||||
g_full = _mm256_sub_epi16(g_full, g_offset2);
|
||||
g_full = _mm256_add_epi16(g_full, c128_offset); // +128 추가
|
||||
g_vec = _mm256_srai_epi16(g_full, 8); // >> 8
|
||||
__m256i r_vec_32 = _mm256_srai_epi32(r_full, 8);
|
||||
__m256i g_vec_32 = _mm256_srai_epi32(g_full, 8);
|
||||
__m256i b_vec_32 = _mm256_srai_epi32(b_full, 8);
|
||||
|
||||
// B 계산: (Y_base + 516 * (U - 128) + 128) >> 8
|
||||
__m256i b_offset = _mm256_mullo_epi16(u_adj, c516);
|
||||
__m256i b_full = _mm256_add_epi16(y_base, b_offset);
|
||||
b_full = _mm256_add_epi16(b_full, c128_offset); // +128 추가
|
||||
b_vec = _mm256_srai_epi16(b_full, 8); // >> 8
|
||||
// --- Pack results ---
|
||||
// Pack 8x 32-bit results back to 8x 16-bit results
|
||||
__m128i r_128_lo = _mm256_castsi256_si128(r_vec_32);
|
||||
__m128i r_128_hi = _mm256_extracti128_si256(r_vec_32, 1);
|
||||
__m128i r_packed_128 = _mm_packs_epi32(r_128_lo, r_128_hi);
|
||||
|
||||
// 클램핑 (0-255)
|
||||
__m256i zero = _mm256_setzero_si256();
|
||||
__m256i max_val = _mm256_set1_epi16(255);
|
||||
__m128i g_128_lo = _mm256_castsi256_si128(g_vec_32);
|
||||
__m128i g_128_hi = _mm256_extracti128_si256(g_vec_32, 1);
|
||||
__m128i g_packed_128 = _mm_packs_epi32(g_128_lo, g_128_hi);
|
||||
|
||||
r_vec = _mm256_max_epi16(r_vec, zero);
|
||||
r_vec = _mm256_min_epi16(r_vec, max_val);
|
||||
__m128i b_128_lo = _mm256_castsi256_si128(b_vec_32);
|
||||
__m128i b_128_hi = _mm256_extracti128_si256(b_vec_32, 1);
|
||||
__m128i b_packed_128 = _mm_packs_epi32(b_128_lo, b_128_hi);
|
||||
|
||||
g_vec = _mm256_max_epi16(g_vec, zero);
|
||||
g_vec = _mm256_min_epi16(g_vec, max_val);
|
||||
// Combine back to 256-bit vectors for clamping
|
||||
r_vec = _mm256_inserti128_si256(_mm256_castsi128_si256(r_packed_128), r_packed_128, 0);
|
||||
g_vec = _mm256_inserti128_si256(_mm256_castsi128_si256(g_packed_128), g_packed_128, 0);
|
||||
b_vec = _mm256_inserti128_si256(_mm256_castsi128_si256(b_packed_128), b_packed_128, 0);
|
||||
|
||||
b_vec = _mm256_max_epi16(b_vec, zero);
|
||||
b_vec = _mm256_min_epi16(b_vec, max_val);
|
||||
// --- Clamp ---
|
||||
const __m256i zero = _mm256_setzero_si256();
|
||||
const __m256i max_val = _mm256_set1_epi16(255);
|
||||
|
||||
r_vec = _mm256_max_epi16(_mm256_min_epi16(r_vec, max_val), zero);
|
||||
g_vec = _mm256_max_epi16(_mm256_min_epi16(g_vec, max_val), zero);
|
||||
b_vec = _mm256_max_epi16(_mm256_min_epi16(b_vec, max_val), zero);
|
||||
}
|
||||
|
||||
// 📈 SIMD RGB24 저장 (인터리브) - 최적화된 버전
|
||||
// SIMD RGB24 storage (interleave) - optimized version
|
||||
void FileOutput::StoreRGB24_SIMD(uint8_t* dst, __m256i r_vec, __m256i g_vec, __m256i b_vec, uint32_t pixel_count) {
|
||||
// 16bit → 8bit 팩킹 (포화 연산으로 0-255 범위 보장)
|
||||
__m128i r_8 = _mm_packus_epi16(_mm256_extracti128_si256(r_vec, 0), _mm256_extracti128_si256(r_vec, 1));
|
||||
__m128i g_8 = _mm_packus_epi16(_mm256_extracti128_si256(g_vec, 0), _mm256_extracti128_si256(g_vec, 1));
|
||||
__m128i b_8 = _mm_packus_epi16(_mm256_extracti128_si256(b_vec, 0), _mm256_extracti128_si256(b_vec, 1));
|
||||
// 16bit -> 8bit packing (with saturation)
|
||||
__m128i r_8 = _mm_packus_epi16(_mm256_castsi256_si128(r_vec), _mm256_extracti128_si256(r_vec, 1));
|
||||
__m128i g_8 = _mm_packus_epi16(_mm256_castsi256_si128(g_vec), _mm256_extracti128_si256(g_vec, 1));
|
||||
__m128i b_8 = _mm_packus_epi16(_mm256_castsi256_si128(b_vec), _mm256_extracti128_si256(b_vec, 1));
|
||||
|
||||
// 최적화된 RGB24 인터리브 저장
|
||||
alignas(16) uint8_t r_array[16], g_array[16], b_array[16];
|
||||
_mm_store_si128((__m128i*)r_array, r_8);
|
||||
_mm_store_si128((__m128i*)g_array, g_8);
|
||||
_mm_store_si128((__m128i*)b_array, b_8);
|
||||
|
||||
// 4픽셀씩 처리하여 캐시 효율성 향상
|
||||
// Process 4 pixels at a time for cache efficiency
|
||||
uint32_t i = 0;
|
||||
for (; i + 3 < pixel_count; i += 4) {
|
||||
// 4픽셀 = 12바이트를 한 번에 처리
|
||||
dst[i * 3 + 0] = r_array[i]; dst[i * 3 + 1] = g_array[i]; dst[i * 3 + 2] = b_array[i];
|
||||
dst[(i+1) * 3 + 0] = r_array[i+1]; dst[(i+1) * 3 + 1] = g_array[i+1]; dst[(i+1) * 3 + 2] = b_array[i+1];
|
||||
dst[(i+2) * 3 + 0] = r_array[i+2]; dst[(i+2) * 3 + 1] = g_array[i+2]; dst[(i+2) * 3 + 2] = b_array[i+2];
|
||||
dst[(i+3) * 3 + 0] = r_array[i+3]; dst[(i+3) * 3 + 1] = g_array[i+3]; dst[(i+3) * 3 + 2] = b_array[i+3];
|
||||
}
|
||||
|
||||
// 나머지 픽셀 처리
|
||||
// Process remaining pixels
|
||||
for (; i < pixel_count; ++i) {
|
||||
dst[i * 3 + 0] = r_array[i];
|
||||
dst[i * 3 + 1] = g_array[i];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "../Common/VideoTypes.h"
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
bool success = false;
|
||||
std::filesystem::path saved_path;
|
||||
size_t file_size_bytes = 0;
|
||||
double conversion_time_ms = 0.0; // YUV->RGB 변환 시간
|
||||
std::string error_message;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user