Files
Xenia-Canary/src/xenia/gpu/gl4/gl4_shader.cc
2015-01-01 11:59:23 -08:00

274 lines
8.7 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <xenia/gpu/gl4/gl4_shader.h>
#include <poly/math.h>
#include <xenia/gpu/gl4/gl4_shader_translator.h>
#include <xenia/gpu/gpu-private.h>
namespace xe {
namespace gpu {
namespace gl4 {
extern "C" GLEWContext* glewGetContext();
// Stateful, but minimally.
thread_local GL4ShaderTranslator shader_translator_;
GL4Shader::GL4Shader(ShaderType shader_type, uint64_t data_hash,
const uint32_t* dword_ptr, uint32_t dword_count)
: Shader(shader_type, data_hash, dword_ptr, dword_count), program_(0) {}
GL4Shader::~GL4Shader() { glDeleteProgram(program_); }
const std::string header =
"#version 450\n"
"#extension all : warn\n"
"#extension GL_ARB_bindless_texture : require\n"
"#extension GL_ARB_explicit_uniform_location : require\n"
"#extension GL_ARB_shading_language_420pack : require\n"
"#extension GL_ARB_shader_storage_buffer_object : require\n"
"precision highp float;\n"
"precision highp int;\n"
"layout(std140, column_major) uniform;\n"
"layout(std430, column_major) buffer;\n"
"struct StateData {\n"
" vec4 window_offset;\n"
" vec4 window_scissor;\n"
" vec4 vtx_fmt;\n"
" vec4 viewport_offset;\n"
" vec4 viewport_scale;\n"
" vec4 alpha_test;\n"
" uvec2 texture_samplers[32];\n"
" vec4 float_consts[512];\n"
" uint fetch_consts[32 * 6];\n"
" int bool_consts[8];\n"
" int loop_consts[32];\n"
"};\n"
"struct VertexData {\n"
" vec4 o[16];\n"
"};\n"
"\n"
"layout(binding = 0) buffer State {\n"
" StateData state;\n"
"};\n";
bool GL4Shader::PrepareVertexShader(
const xenos::xe_gpu_program_cntl_t& program_cntl) {
if (has_prepared_) {
return is_valid_;
}
has_prepared_ = true;
std::string apply_transform =
"vec4 applyTransform(vec4 pos) {\n"
" // Clip->NDC with perspective divide.\n"
" // We do this here because it's programmable on the 360.\n"
" float w = pos.w;\n"
" if (state.vtx_fmt.w == 0.0) {\n"
" // w is not 1/W0. Common case.\n"
" w = 1.0 / w;\n"
" }\n"
" if (state.vtx_fmt.x == 0.0) {\n"
" // Need to multiply by 1/W0.\n"
" pos.xy /= w;\n"
" }\n"
" if (state.vtx_fmt.z == 0.0) {\n"
" // Need to multiply by 1/W0.\n"
" pos.z /= w;\n"
" }\n"
" pos.w = 1.0;\n"
" // Perform clipping, lest we get weird geometry.\n"
// TODO(benvanik): is this right? dxclip mode may change this?
" if (pos.z < gl_DepthRange.near || pos.z > gl_DepthRange.far) {\n"
" // Clipped! w=0 will kill it in the hardware persp divide.\n"
" pos.w = 0.0;\n"
" }\n"
" // NDC transform.\n"
" pos.x = pos.x * state.viewport_scale.x + \n"
" state.viewport_offset.x;\n"
" pos.y = pos.y * state.viewport_scale.y + \n"
" state.viewport_offset.y;\n"
" pos.z = pos.z * state.viewport_scale.z + \n"
" state.viewport_offset.z;\n"
" // NDC->Window with viewport.\n"
" pos.xy = pos.xy * state.window_offset.zw + state.window_offset.xy;\n"
" pos.xy = pos.xy / (vec2(1280.0 - 1.0, -720.0 + 1.0) / 2.0) + vec2(-1.0, 1.0);\n"
" // Window adjustment.\n"
" return pos;\n"
"}\n";
std::string source =
header + apply_transform +
"out gl_PerVertex {\n"
" vec4 gl_Position;\n"
" float gl_PointSize;\n"
" float gl_ClipDistance[];\n"
"};\n"
"layout(location = 0) out VertexData vtx;\n"
"void processVertex();\n"
"void main() {\n" +
(alloc_counts().positions ? " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n"
: "") +
(alloc_counts().point_size ? " gl_PointSize = 1.0;\n" : "") +
" for (int i = 0; i < vtx.o.length(); ++i) {\n"
" vtx.o[i] = vec4(0.0, 0.0, 0.0, 0.0);\n"
" }\n"
" processVertex();\n"
" gl_Position = applyTransform(gl_Position);\n"
"}\n";
std::string translated_source =
shader_translator_.TranslateVertexShader(this, program_cntl);
if (translated_source.empty()) {
PLOGE("Vertex shader failed translation");
return false;
}
source += translated_source;
if (!CompileProgram(source)) {
return false;
}
is_valid_ = true;
return true;
}
bool GL4Shader::PreparePixelShader(
const xenos::xe_gpu_program_cntl_t& program_cntl) {
if (has_prepared_) {
return is_valid_;
}
has_prepared_ = true;
std::string source =
header +
"layout(location = 0) in VertexData vtx;\n"
"layout(location = 0) out vec4 oC[4];\n"
"void processFragment();\n"
"void main() {\n" +
" processFragment();\n"
"}\n";
std::string translated_source =
shader_translator_.TranslatePixelShader(this, program_cntl);
if (translated_source.empty()) {
PLOGE("Pixel shader failed translation");
return false;
}
source += translated_source;
if (!CompileProgram(source)) {
return false;
}
is_valid_ = true;
return true;
}
bool GL4Shader::CompileProgram(std::string source) {
assert_zero(program_);
translated_disassembly_ = std::move(source);
const char* source_str = translated_disassembly_.c_str();
// Save to disk, if we asked for it.
auto base_path = FLAGS_dump_shaders.c_str();
char file_name[poly::max_path];
snprintf(file_name, poly::countof(file_name), "%s/gl4_gen_%.16llX.%s",
base_path, data_hash_,
shader_type_ == ShaderType::kVertex ? "vert" : "frag");
if (FLAGS_dump_shaders.size()) {
// Note that we put the translated source first so we get good line numbers.
FILE* f = fopen(file_name, "w");
fprintf(f, translated_disassembly_.c_str());
fprintf(f, "\n\n");
fprintf(f, "/*\n");
fprintf(f, ucode_disassembly_.c_str());
fprintf(f, " */\n");
fclose(f);
}
program_ = glCreateShaderProgramv(shader_type_ == ShaderType::kVertex
? GL_VERTEX_SHADER
: GL_FRAGMENT_SHADER,
1, &source_str);
if (!program_) {
PLOGE("Unable to create shader program");
return false;
}
// Get error log, if we failed to link.
GLint link_status = 0;
glGetProgramiv(program_, GL_LINK_STATUS, &link_status);
if (!link_status) {
// log_length includes the null character.
GLint log_length = 0;
glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &log_length);
std::string info_log;
info_log.resize(log_length - 1);
glGetProgramInfoLog(program_, log_length, &log_length,
const_cast<char*>(info_log.data()));
PLOGE("Unable to link program: %s", info_log.c_str());
error_log_ = std::move(info_log);
return false;
}
// Get program binary, if it's available.
GLint binary_length = 0;
glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &binary_length);
if (binary_length) {
translated_binary_.resize(binary_length);
GLenum binary_format;
glGetProgramBinary(program_, binary_length, &binary_length, &binary_format,
translated_binary_.data());
// Append to shader dump.
if (FLAGS_dump_shaders.size()) {
// If we are on nvidia, we can find the disassembly string.
// I haven't been able to figure out from the format how to do this
// without a search like this.
const char* disasm_start = nullptr;
size_t search_offset = 0;
char* search_start = reinterpret_cast<char*>(translated_binary_.data());
while (true) {
auto p = reinterpret_cast<char*>(
memchr(translated_binary_.data() + search_offset, '!',
translated_binary_.size() - search_offset));
if (!p) {
break;
}
if (p[0] == '!' && p[1] == '!' && p[2] == 'N' && p[3] == 'V') {
disasm_start = p;
break;
}
search_offset = p - search_start;
++search_offset;
}
if (disasm_start) {
FILE* f = fopen(file_name, "a");
fprintf(f, "\n\n/*\n");
fprintf(f, disasm_start);
fprintf(f, "\n*/\n");
fclose(f);
} else {
PLOGW("Got program binary but unable to find disassembly");
}
}
}
return true;
}
} // namespace gl4
} // namespace gpu
} // namespace xe