mirror of
https://github.com/yuzu-emu/yuzu-android.git
synced 2024-11-26 23:15:39 +01:00
Rewrite of OpenGL renderer, including OS X support
Screen contents are now displayed using textured quads. This can be updated to expose an FBO once an OpenGL backend for when Pica rendering is being worked on. That FBO's texture can then be applied to the quads. Previously, FBO blitting was used in order to display screen contents, which did not work on OS X. The new textured quad approach is less of a compatibility risk.
This commit is contained in:
parent
aa7472057a
commit
cbfd6b6e52
@ -5,8 +5,9 @@ set(SRCS clipper.cpp
|
|||||||
utils.cpp
|
utils.cpp
|
||||||
vertex_shader.cpp
|
vertex_shader.cpp
|
||||||
video_core.cpp
|
video_core.cpp
|
||||||
debug_utils/debug_utils.cpp
|
renderer_opengl/renderer_opengl.cpp
|
||||||
renderer_opengl/renderer_opengl.cpp)
|
renderer_opengl/gl_shader_util.cpp
|
||||||
|
debug_utils/debug_utils.cpp)
|
||||||
|
|
||||||
set(HEADERS clipper.h
|
set(HEADERS clipper.h
|
||||||
command_processor.h
|
command_processor.h
|
||||||
@ -18,7 +19,9 @@ set(HEADERS clipper.h
|
|||||||
renderer_base.h
|
renderer_base.h
|
||||||
vertex_shader.h
|
vertex_shader.h
|
||||||
video_core.h
|
video_core.h
|
||||||
debug_utils/debug_utils.h
|
renderer_opengl/renderer_opengl.h
|
||||||
renderer_opengl/renderer_opengl.h)
|
renderer_opengl/gl_shader_util.h
|
||||||
|
renderer_opengl/gl_shaders.h
|
||||||
|
debug_utils/debug_utils.h)
|
||||||
|
|
||||||
add_library(video_core STATIC ${SRCS} ${HEADERS})
|
add_library(video_core STATIC ${SRCS} ${HEADERS})
|
||||||
|
81
src/video_core/renderer_opengl/gl_shader_util.cpp
Normal file
81
src/video_core/renderer_opengl/gl_shader_util.cpp
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "gl_shader_util.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace ShaderUtil {
|
||||||
|
|
||||||
|
GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) {
|
||||||
|
|
||||||
|
// Create the shaders
|
||||||
|
GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
|
||||||
|
GLint result = GL_FALSE;
|
||||||
|
int info_log_length;
|
||||||
|
|
||||||
|
// Compile Vertex Shader
|
||||||
|
DEBUG_LOG(GPU, "Compiling vertex shader.");
|
||||||
|
|
||||||
|
glShaderSource(vertex_shader_id, 1, &vertex_shader, NULL);
|
||||||
|
glCompileShader(vertex_shader_id);
|
||||||
|
|
||||||
|
// Check Vertex Shader
|
||||||
|
glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
|
||||||
|
glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
|
||||||
|
|
||||||
|
std::vector<char> vertex_shader_error(info_log_length);
|
||||||
|
glGetShaderInfoLog(vertex_shader_id, info_log_length, NULL, &vertex_shader_error[0]);
|
||||||
|
|
||||||
|
if (info_log_length > 1) {
|
||||||
|
DEBUG_LOG(GPU, "%s", &vertex_shader_error[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile Fragment Shader
|
||||||
|
DEBUG_LOG(GPU, "Compiling fragment shader.");
|
||||||
|
|
||||||
|
glShaderSource(fragment_shader_id, 1, &fragment_shader, NULL);
|
||||||
|
glCompileShader(fragment_shader_id);
|
||||||
|
|
||||||
|
// Check Fragment Shader
|
||||||
|
glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
|
||||||
|
glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
|
||||||
|
|
||||||
|
std::vector<char> fragment_shader_error(info_log_length);
|
||||||
|
glGetShaderInfoLog(fragment_shader_id, info_log_length, NULL, &fragment_shader_error[0]);
|
||||||
|
|
||||||
|
if (info_log_length > 1) {
|
||||||
|
DEBUG_LOG(GPU, "%s", &fragment_shader_error[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link the program
|
||||||
|
DEBUG_LOG(GPU, "Linking program.");
|
||||||
|
|
||||||
|
GLuint program_id = glCreateProgram();
|
||||||
|
glAttachShader(program_id, vertex_shader_id);
|
||||||
|
glAttachShader(program_id, fragment_shader_id);
|
||||||
|
glLinkProgram(program_id);
|
||||||
|
|
||||||
|
// Check the program
|
||||||
|
glGetProgramiv(program_id, GL_LINK_STATUS, &result);
|
||||||
|
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_length);
|
||||||
|
|
||||||
|
std::vector<char> program_error(std::max(info_log_length, int(1)));
|
||||||
|
glGetProgramInfoLog(program_id, info_log_length, NULL, &program_error[0]);
|
||||||
|
|
||||||
|
if (info_log_length > 1) {
|
||||||
|
DEBUG_LOG(GPU, "%s", &program_error[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
glDeleteShader(vertex_shader_id);
|
||||||
|
glDeleteShader(fragment_shader_id);
|
||||||
|
|
||||||
|
return program_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/video_core/renderer_opengl/gl_shader_util.h
Normal file
13
src/video_core/renderer_opengl/gl_shader_util.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
namespace ShaderUtil {
|
||||||
|
|
||||||
|
GLuint LoadShaders(const char* vertex_file_path, const char* fragment_file_path);
|
||||||
|
|
||||||
|
}
|
39
src/video_core/renderer_opengl/gl_shaders.h
Normal file
39
src/video_core/renderer_opengl/gl_shaders.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace GLShaders {
|
||||||
|
|
||||||
|
static const char g_vertex_shader[] = R"(
|
||||||
|
#version 330 core
|
||||||
|
layout(location = 0) in vec3 position;
|
||||||
|
layout(location = 1) in vec2 texCoord;
|
||||||
|
|
||||||
|
out vec2 UV;
|
||||||
|
|
||||||
|
mat3 window_scale = mat3(
|
||||||
|
vec3(1.0, 0.0, 0.0),
|
||||||
|
vec3(0.0, 5.0/6.0, 0.0), // TODO(princesspeachum): replace hard-coded aspect with uniform
|
||||||
|
vec3(0.0, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position.xyz = window_scale * position;
|
||||||
|
gl_Position.w = 1.0;
|
||||||
|
|
||||||
|
UV = texCoord;
|
||||||
|
})";
|
||||||
|
|
||||||
|
static const char g_fragment_shader[] = R"(
|
||||||
|
#version 330 core
|
||||||
|
in vec2 UV;
|
||||||
|
out vec3 color;
|
||||||
|
uniform sampler2D sampler;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = texture(sampler, UV).rgb;
|
||||||
|
})";
|
||||||
|
|
||||||
|
}
|
@ -6,24 +6,56 @@
|
|||||||
|
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_shaders.h"
|
||||||
|
|
||||||
#include "core/mem_map.h"
|
#include "core/mem_map.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
static const GLfloat kViewportAspectRatio =
|
||||||
|
(static_cast<float>(VideoCore::kScreenTopHeight) + VideoCore::kScreenBottomHeight) / VideoCore::kScreenTopWidth;
|
||||||
|
|
||||||
|
// Fullscreen quad dimensions
|
||||||
|
static const GLfloat kTopScreenWidthNormalized = 2;
|
||||||
|
static const GLfloat kTopScreenHeightNormalized = kTopScreenWidthNormalized * (static_cast<float>(VideoCore::kScreenTopHeight) / VideoCore::kScreenTopWidth);
|
||||||
|
static const GLfloat kBottomScreenWidthNormalized = kTopScreenWidthNormalized * (static_cast<float>(VideoCore::kScreenBottomWidth) / VideoCore::kScreenTopWidth);
|
||||||
|
static const GLfloat kBottomScreenHeightNormalized = kBottomScreenWidthNormalized * (static_cast<float>(VideoCore::kScreenBottomHeight) / VideoCore::kScreenBottomWidth);
|
||||||
|
|
||||||
|
static const GLfloat g_vbuffer_top[] = {
|
||||||
|
// x, y, z u, v
|
||||||
|
-1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
|
||||||
|
1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
|
||||||
|
1.0f, kTopScreenHeightNormalized, 0.0f, 1.0f, 0.0f,
|
||||||
|
1.0f, kTopScreenHeightNormalized, 0.0f, 1.0f, 0.0f,
|
||||||
|
-1.0f, kTopScreenHeightNormalized, 0.0f, 0.0f, 0.0f,
|
||||||
|
-1.0f, 0.0f, 0.0f, 0.0f, 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
static const GLfloat g_vbuffer_bottom[] = {
|
||||||
|
// x, y, z u, v
|
||||||
|
-(kBottomScreenWidthNormalized / 2), -kBottomScreenHeightNormalized, 0.0f, 0.0f, 1.0f,
|
||||||
|
(kBottomScreenWidthNormalized / 2), -kBottomScreenHeightNormalized, 0.0f, 1.0f, 1.0f,
|
||||||
|
(kBottomScreenWidthNormalized / 2), 0.0f, 0.0f, 1.0f, 0.0f,
|
||||||
|
(kBottomScreenWidthNormalized / 2), 0.0f, 0.0f, 1.0f, 0.0f,
|
||||||
|
-(kBottomScreenWidthNormalized / 2), 0.0f, 0.0f, 0.0f, 0.0f,
|
||||||
|
-(kBottomScreenWidthNormalized / 2), -kBottomScreenHeightNormalized, 0.0f, 0.0f, 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
/// RendererOpenGL constructor
|
/// RendererOpenGL constructor
|
||||||
RendererOpenGL::RendererOpenGL() {
|
RendererOpenGL::RendererOpenGL() {
|
||||||
memset(m_fbo, 0, sizeof(m_fbo));
|
|
||||||
memset(m_fbo_rbo, 0, sizeof(m_fbo_rbo));
|
|
||||||
memset(m_fbo_depth_buffers, 0, sizeof(m_fbo_depth_buffers));
|
|
||||||
|
|
||||||
m_resolution_width = max(VideoCore::kScreenTopWidth, VideoCore::kScreenBottomWidth);
|
resolution_width = std::max(VideoCore::kScreenTopWidth, VideoCore::kScreenBottomWidth);
|
||||||
m_resolution_height = VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight;
|
resolution_height = VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight;
|
||||||
|
|
||||||
m_xfb_texture_top = 0;
|
// Initialize screen info
|
||||||
m_xfb_texture_bottom = 0;
|
screen_info.Top().width = VideoCore::kScreenTopWidth;
|
||||||
|
screen_info.Top().height = VideoCore::kScreenTopHeight;
|
||||||
|
screen_info.Top().flipped_xfb_data = xfb_top_flipped;
|
||||||
|
|
||||||
m_xfb_top = 0;
|
screen_info.Bottom().width = VideoCore::kScreenBottomWidth;
|
||||||
m_xfb_bottom = 0;
|
screen_info.Bottom().height = VideoCore::kScreenBottomHeight;
|
||||||
|
screen_info.Bottom().flipped_xfb_data = xfb_bottom_flipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RendererOpenGL destructor
|
/// RendererOpenGL destructor
|
||||||
@ -32,41 +64,41 @@ RendererOpenGL::~RendererOpenGL() {
|
|||||||
|
|
||||||
/// Swap buffers (render frame)
|
/// Swap buffers (render frame)
|
||||||
void RendererOpenGL::SwapBuffers() {
|
void RendererOpenGL::SwapBuffers() {
|
||||||
m_render_window->MakeCurrent();
|
render_window->MakeCurrent();
|
||||||
|
|
||||||
// EFB->XFB copy
|
// EFB->XFB copy
|
||||||
// TODO(bunnei): This is a hack and does not belong here. The copy should be triggered by some
|
// TODO(bunnei): This is a hack and does not belong here. The copy should be triggered by some
|
||||||
// register write We're also treating both framebuffers as a single one in OpenGL.
|
// register write.
|
||||||
common::Rect framebuffer_size(0, 0, m_resolution_width, m_resolution_height);
|
//
|
||||||
|
// TODO(princesspeachum): (related to above^) this should only be called when there's new data, not every frame.
|
||||||
|
// Currently this uploads data that shouldn't have changed.
|
||||||
|
common::Rect framebuffer_size(0, 0, resolution_width, resolution_height);
|
||||||
RenderXFB(framebuffer_size, framebuffer_size);
|
RenderXFB(framebuffer_size, framebuffer_size);
|
||||||
|
|
||||||
// XFB->Window copy
|
// XFB->Window copy
|
||||||
RenderFramebuffer();
|
RenderFramebuffer();
|
||||||
|
|
||||||
// Swap buffers
|
// Swap buffers
|
||||||
m_render_window->PollEvents();
|
render_window->PollEvents();
|
||||||
m_render_window->SwapBuffers();
|
render_window->SwapBuffers();
|
||||||
|
|
||||||
// Switch back to EFB and clear
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo[kFramebuffer_EFB]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to flip framebuffer from left-to-right to top-to-bottom
|
* Helper function to flip framebuffer from left-to-right to top-to-bottom
|
||||||
* @param in Pointer to input raw framebuffer in V/RAM
|
* @param raw_data Pointer to input raw framebuffer in V/RAM
|
||||||
* @param out Pointer to output buffer with flipped framebuffer
|
* @param screen_info ScreenInfo structure with screen size and output buffer pointer
|
||||||
* @todo Early on hack... I'd like to find a more efficient way of doing this /bunnei
|
* @todo Early on hack... I'd like to find a more efficient way of doing this /bunnei
|
||||||
*/
|
*/
|
||||||
void RendererOpenGL::FlipFramebuffer(const u8* in, u8* out) {
|
void RendererOpenGL::FlipFramebuffer(const u8* raw_data, ScreenInfo& screen_info) {
|
||||||
int in_coord = 0;
|
int in_coord = 0;
|
||||||
for (int x = 0; x < VideoCore::kScreenTopWidth; x++) {
|
for (int x = 0; x < screen_info.width; x++) {
|
||||||
for (int y = VideoCore::kScreenTopHeight-1; y >= 0; y--) {
|
for (int y = screen_info.height-1; y >= 0; y--) {
|
||||||
// TODO: Properly support other framebuffer formats
|
// TODO: Properly support other framebuffer formats
|
||||||
int out_coord = (x + y * VideoCore::kScreenTopWidth) * 3;
|
int out_coord = (x + y * screen_info.width) * 3;
|
||||||
out[out_coord] = in[in_coord]; // blue?
|
screen_info.flipped_xfb_data[out_coord] = raw_data[in_coord + 2]; // Red
|
||||||
out[out_coord + 1] = in[in_coord + 1]; // green?
|
screen_info.flipped_xfb_data[out_coord + 1] = raw_data[in_coord + 1]; // Green
|
||||||
out[out_coord + 2] = in[in_coord + 2]; // red?
|
screen_info.flipped_xfb_data[out_coord + 2] = raw_data[in_coord]; // Blue
|
||||||
in_coord+=3;
|
in_coord += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,167 +109,116 @@ void RendererOpenGL::FlipFramebuffer(const u8* in, u8* out) {
|
|||||||
* @param dst_rect Destination rectangle in output framebuffer to copy to
|
* @param dst_rect Destination rectangle in output framebuffer to copy to
|
||||||
*/
|
*/
|
||||||
void RendererOpenGL::RenderXFB(const common::Rect& src_rect, const common::Rect& dst_rect) {
|
void RendererOpenGL::RenderXFB(const common::Rect& src_rect, const common::Rect& dst_rect) {
|
||||||
|
|
||||||
const auto& framebuffer_top = GPU::g_regs.framebuffer_config[0];
|
const auto& framebuffer_top = GPU::g_regs.framebuffer_config[0];
|
||||||
const auto& framebuffer_sub = GPU::g_regs.framebuffer_config[1];
|
const auto& framebuffer_sub = GPU::g_regs.framebuffer_config[1];
|
||||||
const u32 active_fb_top = (framebuffer_top.active_fb == 1)
|
const u32 active_fb_top = (framebuffer_top.active_fb == 1)
|
||||||
? Memory::PhysicalToVirtualAddress(framebuffer_top.address_left2)
|
? Memory::PhysicalToVirtualAddress(framebuffer_top.address_left2)
|
||||||
: Memory::PhysicalToVirtualAddress(framebuffer_top.address_left1);
|
: Memory::PhysicalToVirtualAddress(framebuffer_top.address_left1);
|
||||||
const u32 active_fb_sub = (framebuffer_sub.active_fb == 1)
|
const u32 active_fb_sub = (framebuffer_sub.active_fb == 1)
|
||||||
? Memory::PhysicalToVirtualAddress(framebuffer_sub.address_left2)
|
? Memory::PhysicalToVirtualAddress(framebuffer_sub.address_left2)
|
||||||
: Memory::PhysicalToVirtualAddress(framebuffer_sub.address_left1);
|
: Memory::PhysicalToVirtualAddress(framebuffer_sub.address_left1);
|
||||||
|
|
||||||
DEBUG_LOG(GPU, "RenderXFB: 0x%08x bytes from 0x%08x(%dx%d), fmt %x",
|
DEBUG_LOG(GPU, "RenderXFB: 0x%08x bytes from 0x%08x(%dx%d), fmt %x",
|
||||||
framebuffer_top.stride * framebuffer_top.height,
|
framebuffer_top.stride * framebuffer_top.height,
|
||||||
active_fb_top, (int)framebuffer_top.width,
|
active_fb_top, (int)framebuffer_top.width,
|
||||||
(int)framebuffer_top.height, (int)framebuffer_top.format);
|
(int)framebuffer_top.height, (int)framebuffer_top.format);
|
||||||
|
|
||||||
// TODO: This should consider the GPU registers for framebuffer width, height and stride.
|
FlipFramebuffer(Memory::GetPointer(active_fb_top), screen_info.Top());
|
||||||
FlipFramebuffer(Memory::GetPointer(active_fb_top), m_xfb_top_flipped);
|
FlipFramebuffer(Memory::GetPointer(active_fb_sub), screen_info.Bottom());
|
||||||
FlipFramebuffer(Memory::GetPointer(active_fb_sub), m_xfb_bottom_flipped);
|
|
||||||
|
|
||||||
// Blit the top framebuffer
|
for (int i = 0; i < 2; i++) {
|
||||||
// ------------------------
|
ScreenInfo* current_screen = &screen_info[i];
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, current_screen->texture_id);
|
||||||
|
|
||||||
|
// TODO: This should consider the GPU registers for framebuffer width, height and stride.
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, current_screen->width, current_screen->height,
|
||||||
|
GL_RGB, GL_UNSIGNED_BYTE, current_screen->flipped_xfb_data);
|
||||||
|
}
|
||||||
|
|
||||||
// Update textures with contents of XFB in RAM - top
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_xfb_texture_top);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight,
|
|
||||||
GL_BGR, GL_UNSIGNED_BYTE, m_xfb_top_flipped);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
// Render target is destination framebuffer
|
// TODO(princesspeachum):
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo[kFramebuffer_VirtualXFB]);
|
// Only the subset src_rect of the GPU buffer
|
||||||
glViewport(0, 0, VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight);
|
// should be copied into the texture of the relevant screen.
|
||||||
|
//
|
||||||
// Render source is our EFB
|
// The method's parameters also only include src_rect and dest_rec for one screen,
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_xfb_top);
|
// so this may need to be changed (pair for each screen).
|
||||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
||||||
|
|
||||||
// Blit
|
|
||||||
glBlitFramebuffer(src_rect.x0_, src_rect.y0_, src_rect.x1_, src_rect.y1_,
|
|
||||||
dst_rect.x0_, dst_rect.y1_, dst_rect.x1_, dst_rect.y0_,
|
|
||||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
// Blit the bottom framebuffer
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
// Update textures with contents of XFB in RAM - bottom
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_xfb_texture_bottom);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight,
|
|
||||||
GL_BGR, GL_UNSIGNED_BYTE, m_xfb_bottom_flipped);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
// Render target is destination framebuffer
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo[kFramebuffer_VirtualXFB]);
|
|
||||||
glViewport(0, 0,
|
|
||||||
VideoCore::kScreenBottomWidth, VideoCore::kScreenBottomHeight);
|
|
||||||
|
|
||||||
// Render source is our EFB
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_xfb_bottom);
|
|
||||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
||||||
|
|
||||||
// Blit
|
|
||||||
int offset = (VideoCore::kScreenTopWidth - VideoCore::kScreenBottomWidth) / 2;
|
|
||||||
glBlitFramebuffer(0,0, VideoCore::kScreenBottomWidth, VideoCore::kScreenBottomHeight,
|
|
||||||
offset, VideoCore::kScreenBottomHeight, VideoCore::kScreenBottomWidth + offset, 0,
|
|
||||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the FBO
|
/// Initialize the FBO
|
||||||
void RendererOpenGL::InitFramebuffer() {
|
void RendererOpenGL::InitFramebuffer() {
|
||||||
// TODO(bunnei): This should probably be implemented with the top screen and bottom screen as
|
program_id = ShaderUtil::LoadShaders(GLShaders::g_vertex_shader, GLShaders::g_fragment_shader);
|
||||||
// separate framebuffers
|
sampler_id = glGetUniformLocation(program_id, "sampler");
|
||||||
|
|
||||||
// Init the FBOs
|
// Generate vertex buffers for both screens
|
||||||
// -------------
|
glGenBuffers(1, &screen_info.Top().vertex_buffer_id);
|
||||||
|
glGenBuffers(1, &screen_info.Bottom().vertex_buffer_id);
|
||||||
|
|
||||||
glGenFramebuffers(kMaxFramebuffers, m_fbo); // Generate primary framebuffer
|
// Attach vertex data for top screen
|
||||||
glGenRenderbuffers(kMaxFramebuffers, m_fbo_rbo); // Generate primary RBOs
|
glBindBuffer(GL_ARRAY_BUFFER, screen_info.Top().vertex_buffer_id);
|
||||||
glGenRenderbuffers(kMaxFramebuffers, m_fbo_depth_buffers); // Generate primary depth buffer
|
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vbuffer_top), g_vbuffer_top, GL_STATIC_DRAW);
|
||||||
|
|
||||||
for (int i = 0; i < kMaxFramebuffers; i++) {
|
// Attach vertex data for bottom screen
|
||||||
// Generate color buffer storage
|
glBindBuffer(GL_ARRAY_BUFFER, screen_info.Bottom().vertex_buffer_id);
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, m_fbo_rbo[i]);
|
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vbuffer_bottom), g_vbuffer_bottom, GL_STATIC_DRAW);
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, VideoCore::kScreenTopWidth,
|
|
||||||
VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
|
|
||||||
|
|
||||||
// Generate depth buffer storage
|
// Create color buffers for both screens
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, m_fbo_depth_buffers[i]);
|
glGenTextures(1, &screen_info.Top().texture_id);
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, VideoCore::kScreenTopWidth,
|
glGenTextures(1, &screen_info.Bottom().texture_id);
|
||||||
VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
|
|
||||||
|
|
||||||
// Attach the buffers
|
for (int i = 0; i < 2; i++) {
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo[i]);
|
|
||||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
||||||
GL_RENDERBUFFER, m_fbo_depth_buffers[i]);
|
|
||||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
||||||
GL_RENDERBUFFER, m_fbo_rbo[i]);
|
|
||||||
|
|
||||||
// Check for completeness
|
ScreenInfo* current_screen = &screen_info[i];
|
||||||
if (GL_FRAMEBUFFER_COMPLETE == glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)) {
|
|
||||||
NOTICE_LOG(RENDER, "framebuffer(%d) initialized ok", i);
|
// Allocate texture
|
||||||
} else {
|
glBindTexture(GL_TEXTURE_2D, current_screen->vertex_buffer_id);
|
||||||
ERROR_LOG(RENDER, "couldn't create OpenGL frame buffer");
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, current_screen->width, current_screen->height,
|
||||||
exit(1);
|
0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
||||||
}
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
}
|
}
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind our frame buffer(s)
|
|
||||||
|
|
||||||
// Initialize framebuffer textures
|
|
||||||
// -------------------------------
|
|
||||||
|
|
||||||
// Create XFB textures
|
|
||||||
glGenTextures(1, &m_xfb_texture_top);
|
|
||||||
glGenTextures(1, &m_xfb_texture_bottom);
|
|
||||||
|
|
||||||
// Alocate video memorry for XFB textures
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_xfb_texture_top);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight,
|
|
||||||
0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_xfb_texture_bottom);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight,
|
|
||||||
0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
// Create the FBO and attach color/depth textures
|
|
||||||
glGenFramebuffers(1, &m_xfb_top); // Generate framebuffer
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_xfb_top);
|
|
||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
||||||
m_xfb_texture_top, 0);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
glGenFramebuffers(1, &m_xfb_bottom); // Generate framebuffer
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_xfb_bottom);
|
|
||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
||||||
m_xfb_texture_bottom, 0);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blit the FBO to the OpenGL default framebuffer
|
|
||||||
void RendererOpenGL::RenderFramebuffer() {
|
void RendererOpenGL::RenderFramebuffer() {
|
||||||
// Render target is default framebuffer
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
||||||
glViewport(0, 0, m_resolution_width, m_resolution_height);
|
|
||||||
|
|
||||||
// Render source is our XFB
|
glUseProgram(program_id);
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo[kFramebuffer_VirtualXFB]);
|
|
||||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
||||||
|
|
||||||
// Blit
|
// Bind texture in Texture Unit 0
|
||||||
glBlitFramebuffer(0, 0, m_resolution_width, m_resolution_height, 0, 0, m_resolution_width,
|
glActiveTexture(GL_TEXTURE0);
|
||||||
m_resolution_height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
|
|
||||||
// Update the FPS count
|
glEnableVertexAttribArray(0);
|
||||||
UpdateFramerate();
|
glEnableVertexAttribArray(1);
|
||||||
|
|
||||||
// Rebind EFB
|
for (int i = 0; i < 2; i++) {
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo[kFramebuffer_EFB]);
|
|
||||||
|
ScreenInfo* current_screen = &screen_info[i];
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, current_screen->texture_id);
|
||||||
|
|
||||||
|
// Set sampler on Texture Unit 0
|
||||||
|
glUniform1i(sampler_id, 0);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, current_screen->vertex_buffer_id);
|
||||||
|
|
||||||
|
// Vertex buffer layout
|
||||||
|
const GLsizei stride = 5 * sizeof(GLfloat);
|
||||||
|
const GLvoid* uv_offset = (const GLvoid*)(3 * sizeof(GLfloat));
|
||||||
|
|
||||||
|
// Configure vertex buffer
|
||||||
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, NULL);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, uv_offset);
|
||||||
|
|
||||||
|
// Draw screen
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
glDisableVertexAttribArray(0);
|
||||||
|
glDisableVertexAttribArray(1);
|
||||||
|
|
||||||
m_current_frame++;
|
m_current_frame++;
|
||||||
}
|
}
|
||||||
@ -251,40 +232,29 @@ void RendererOpenGL::UpdateFramerate() {
|
|||||||
* @param window EmuWindow handle to emulator window to use for rendering
|
* @param window EmuWindow handle to emulator window to use for rendering
|
||||||
*/
|
*/
|
||||||
void RendererOpenGL::SetWindow(EmuWindow* window) {
|
void RendererOpenGL::SetWindow(EmuWindow* window) {
|
||||||
m_render_window = window;
|
render_window = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the renderer
|
/// Initialize the renderer
|
||||||
void RendererOpenGL::Init() {
|
void RendererOpenGL::Init() {
|
||||||
m_render_window->MakeCurrent();
|
render_window->MakeCurrent();
|
||||||
glShadeModel(GL_SMOOTH);
|
|
||||||
|
|
||||||
|
|
||||||
glStencilFunc(GL_ALWAYS, 0, 0);
|
|
||||||
glBlendFunc(GL_ONE, GL_ONE);
|
|
||||||
|
|
||||||
glViewport(0, 0, m_resolution_width, m_resolution_height);
|
|
||||||
|
|
||||||
glClearDepth(1.0f);
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glDisable(GL_LIGHTING);
|
|
||||||
glDepthFunc(GL_LEQUAL);
|
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
|
|
||||||
glDisable(GL_STENCIL_TEST);
|
|
||||||
glEnable(GL_SCISSOR_TEST);
|
|
||||||
|
|
||||||
glScissor(0, 0, m_resolution_width, m_resolution_height);
|
|
||||||
glClearDepth(1.0f);
|
|
||||||
|
|
||||||
GLenum err = glewInit();
|
GLenum err = glewInit();
|
||||||
if (GLEW_OK != err) {
|
if (GLEW_OK != err) {
|
||||||
ERROR_LOG(RENDER, "Failed to initialize GLEW! Error message: \"%s\". Exiting...",
|
ERROR_LOG(RENDER, "Failed to initialize GLEW! Error message: \"%s\". Exiting...",
|
||||||
glewGetErrorString(err));
|
glewGetErrorString(err));
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate VAO
|
||||||
|
glGenVertexArrays(1, &vertex_array_id);
|
||||||
|
glBindVertexArray(vertex_array_id);
|
||||||
|
|
||||||
|
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
// Initialize everything else
|
// Initialize everything else
|
||||||
// --------------------------
|
// --------------------------
|
||||||
|
|
||||||
|
@ -11,12 +11,11 @@
|
|||||||
|
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
class RendererOpenGL : virtual public RendererBase {
|
class RendererOpenGL : virtual public RendererBase {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static const int kMaxFramebuffers = 2; ///< Maximum number of framebuffers
|
|
||||||
|
|
||||||
RendererOpenGL();
|
RendererOpenGL();
|
||||||
~RendererOpenGL();
|
~RendererOpenGL();
|
||||||
|
|
||||||
@ -53,37 +52,47 @@ private:
|
|||||||
/// Updates the framerate
|
/// Updates the framerate
|
||||||
void UpdateFramerate();
|
void UpdateFramerate();
|
||||||
|
|
||||||
|
/// Structure used for storing information for rendering each 3DS screen
|
||||||
|
struct ScreenInfo {
|
||||||
|
// Properties
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
// OpenGL object IDs
|
||||||
|
GLuint texture_id;
|
||||||
|
GLuint vertex_buffer_id;
|
||||||
|
|
||||||
|
// Temporary
|
||||||
|
u8* flipped_xfb_data;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to flip framebuffer from left-to-right to top-to-bottom
|
* Helper function to flip framebuffer from left-to-right to top-to-bottom
|
||||||
* @param in Pointer to input raw framebuffer in V/RAM
|
* @param raw_data Pointer to input raw framebuffer in V/RAM
|
||||||
* @param out Pointer to output buffer with flipped framebuffer
|
* @param screen_info ScreenInfo structure with screen size and output buffer pointer
|
||||||
* @todo Early on hack... I'd like to find a more efficient way of doing this /bunnei
|
* @todo Early on hack... I'd like to find a more efficient way of doing this /bunnei
|
||||||
*/
|
*/
|
||||||
void FlipFramebuffer(const u8* in, u8* out);
|
void FlipFramebuffer(const u8* raw_data, ScreenInfo& screen_info);
|
||||||
|
|
||||||
|
EmuWindow* render_window; ///< Handle to render window
|
||||||
|
u32 last_mode; ///< Last render mode
|
||||||
|
|
||||||
EmuWindow* m_render_window; ///< Handle to render window
|
int resolution_width; ///< Current resolution width
|
||||||
u32 m_last_mode; ///< Last render mode
|
int resolution_height; ///< Current resolution height
|
||||||
|
|
||||||
int m_resolution_width; ///< Current resolution width
|
// OpenGL global object IDs
|
||||||
int m_resolution_height; ///< Current resolution height
|
GLuint vertex_array_id;
|
||||||
|
GLuint program_id;
|
||||||
|
GLuint sampler_id;
|
||||||
|
|
||||||
// Framebuffers
|
struct : std::array<ScreenInfo, 2> {
|
||||||
// ------------
|
ScreenInfo& Top() { return (*this)[0]; }
|
||||||
|
ScreenInfo& Bottom() { return (*this)[1]; }
|
||||||
GLuint m_fbo[kMaxFramebuffers]; ///< Framebuffer objects
|
} screen_info;
|
||||||
GLuint m_fbo_rbo[kMaxFramebuffers]; ///< Render buffer objects
|
|
||||||
GLuint m_fbo_depth_buffers[kMaxFramebuffers]; ///< Depth buffers objects
|
|
||||||
|
|
||||||
GLuint m_xfb_texture_top; ///< GL handle to top framebuffer texture
|
|
||||||
GLuint m_xfb_texture_bottom; ///< GL handle to bottom framebuffer texture
|
|
||||||
|
|
||||||
GLuint m_xfb_top; ///< GL handle to top framebuffer
|
|
||||||
GLuint m_xfb_bottom; ///< GL handle to bottom framebuffer
|
|
||||||
|
|
||||||
// "Flipped" framebuffers translate scanlines from native 3DS left-to-right to top-to-bottom
|
// "Flipped" framebuffers translate scanlines from native 3DS left-to-right to top-to-bottom
|
||||||
// as OpenGL expects them in a texture. There probably is a more efficient way of doing this:
|
// as OpenGL expects them in a texture. There probably is a more efficient way of doing this:
|
||||||
|
u8 xfb_top_flipped[VideoCore::kScreenTopWidth * VideoCore::kScreenTopHeight * 4];
|
||||||
|
u8 xfb_bottom_flipped[VideoCore::kScreenBottomWidth * VideoCore::kScreenBottomHeight * 4];
|
||||||
|
|
||||||
u8 m_xfb_top_flipped[VideoCore::kScreenTopWidth * VideoCore::kScreenTopHeight * 4];
|
|
||||||
u8 m_xfb_bottom_flipped[VideoCore::kScreenBottomWidth * VideoCore::kScreenBottomHeight * 4];
|
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
<ProjectConfiguration Include="Debug|Win32">
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
@ -21,6 +21,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="debug_utils\debug_utils.cpp" />
|
<ClCompile Include="debug_utils\debug_utils.cpp" />
|
||||||
<ClCompile Include="renderer_opengl\renderer_opengl.cpp" />
|
<ClCompile Include="renderer_opengl\renderer_opengl.cpp" />
|
||||||
|
<ClCompile Include="renderer_opengl\gl_shader_util.cpp" />
|
||||||
<ClCompile Include="clipper.cpp" />
|
<ClCompile Include="clipper.cpp" />
|
||||||
<ClCompile Include="command_processor.cpp" />
|
<ClCompile Include="command_processor.cpp" />
|
||||||
<ClCompile Include="primitive_assembly.cpp" />
|
<ClCompile Include="primitive_assembly.cpp" />
|
||||||
@ -43,6 +44,8 @@
|
|||||||
<ClInclude Include="video_core.h" />
|
<ClInclude Include="video_core.h" />
|
||||||
<ClInclude Include="debug_utils\debug_utils.h" />
|
<ClInclude Include="debug_utils\debug_utils.h" />
|
||||||
<ClInclude Include="renderer_opengl\renderer_opengl.h" />
|
<ClInclude Include="renderer_opengl\renderer_opengl.h" />
|
||||||
|
<ClInclude Include="renderer_opengl\gl_shader_util.h" />
|
||||||
|
<ClInclude Include="renderer_opengl\gl_shaders.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="renderer_opengl">
|
<Filter Include="renderer_opengl">
|
||||||
@ -12,6 +12,9 @@
|
|||||||
<ClCompile Include="renderer_opengl\renderer_opengl.cpp">
|
<ClCompile Include="renderer_opengl\renderer_opengl.cpp">
|
||||||
<Filter>renderer_opengl</Filter>
|
<Filter>renderer_opengl</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="renderer_opengl\gl_shader_util.cpp">
|
||||||
|
<Filter>renderer_opengl</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="clipper.cpp" />
|
<ClCompile Include="clipper.cpp" />
|
||||||
<ClCompile Include="command_processor.cpp" />
|
<ClCompile Include="command_processor.cpp" />
|
||||||
<ClCompile Include="primitive_assembly.cpp" />
|
<ClCompile Include="primitive_assembly.cpp" />
|
||||||
@ -35,7 +38,15 @@
|
|||||||
<ClInclude Include="utils.h" />
|
<ClInclude Include="utils.h" />
|
||||||
<ClInclude Include="vertex_shader.h" />
|
<ClInclude Include="vertex_shader.h" />
|
||||||
<ClInclude Include="video_core.h" />
|
<ClInclude Include="video_core.h" />
|
||||||
<ClInclude Include="renderer_opengl\renderer_opengl.h" />
|
<ClInclude Include="renderer_opengl\renderer_opengl.h">
|
||||||
|
<Filter>renderer_opengl</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="renderer_opengl\gl_shader_util.h">
|
||||||
|
<Filter>renderer_opengl</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="renderer_opengl\gl_shaders.h">
|
||||||
|
<Filter>renderer_opengl</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="debug_utils\debug_utils.h">
|
<ClInclude Include="debug_utils\debug_utils.h">
|
||||||
<Filter>debug_utils</Filter>
|
<Filter>debug_utils</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
Loading…
Reference in New Issue
Block a user