mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-31 03:39:04 +08:00
3186 lines
115 KiB
C++
3186 lines
115 KiB
C++
// Released under the MIT License. See LICENSE for details.
|
|
|
|
#if BA_ENABLE_OPENGL
|
|
#include "ballistica/base/graphics/gl/renderer_gl.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <iterator>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "ballistica/base/graphics/component/special_component.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_asset_data_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_dual_texture_full_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_object_split_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_simple_full_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_simple_split_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_smoke_full_gl.h"
|
|
#include "ballistica/base/graphics/gl/mesh/mesh_data_sprite_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_blur_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_object_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_post_process_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_shield_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_simple_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_smoke_gl.h"
|
|
#include "ballistica/base/graphics/gl/program/program_sprite_gl.h"
|
|
#include "ballistica/base/graphics/gl/render_target_gl.h"
|
|
#include "ballistica/base/graphics/gl/texture_data_gl.h"
|
|
#include "ballistica/shared/math/rect.h"
|
|
|
|
// Turn this off to see how much blend overdraw is occurring.
|
|
#define BA_GL_ENABLE_BLEND 1
|
|
|
|
// Support legacy drawing purely for debugging (should migrate this to
|
|
// post-fixed pipeline).
|
|
#define BA_GL_ENABLE_DEBUG_DRAW_COMMANDS 0
|
|
|
|
#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
|
|
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
|
|
#endif
|
|
#ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
|
|
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
|
|
#endif
|
|
|
|
#ifndef GL_ETC1_RGB8_OES
|
|
#define GL_ETC1_RGB8_OES 0x8D64
|
|
#endif
|
|
|
|
#ifndef GL_COMPRESSED_RGB8_ETC2
|
|
#define GL_COMPRESSED_RGB8_ETC2 0x9274
|
|
#endif
|
|
#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC
|
|
#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
|
|
#endif
|
|
|
|
namespace ballistica::base {
|
|
|
|
bool RendererGL::funky_depth_issue_set_{};
|
|
bool RendererGL::funky_depth_issue_{};
|
|
|
|
RendererGL::RendererGL() {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
|
|
if (explicit_bool(BA_FORCE_CHECK_GL_ERRORS)) {
|
|
ScreenMessage("GL ERROR CHECKS ENABLED");
|
|
}
|
|
|
|
// Run any one-time setup the platform might need to do
|
|
// (grabbing function pointers, etc.)
|
|
if (!g_sys_gl_inited) {
|
|
SysGLInit(this);
|
|
g_sys_gl_inited = true;
|
|
}
|
|
|
|
CheckGLCapabilities_();
|
|
SyncGLState_();
|
|
}
|
|
|
|
void RendererGL::CheckGLError(const char* file, int line) {
|
|
GLenum err = glGetError();
|
|
if (err != GL_NO_ERROR) {
|
|
const char* version = (const char*)glGetString(GL_VERSION);
|
|
BA_PRECONDITION_FATAL(version);
|
|
const char* vendor = (const char*)glGetString(GL_VENDOR);
|
|
BA_PRECONDITION_FATAL(vendor);
|
|
const char* renderer = (const char*)glGetString(GL_RENDERER);
|
|
BA_PRECONDITION_FATAL(renderer);
|
|
Log(LogName::kBaGraphics, LogLevel::kError,
|
|
"OpenGL Error at " + std::string(file) + " line " + std::to_string(line)
|
|
+ ": " + GLErrorToString(err) + "\nrenderer: " + renderer
|
|
+ "\nvendor: " + vendor + "\nversion: " + version
|
|
+ "\ntime: " + std::to_string(g_core->GetAppTimeMillisecs()));
|
|
}
|
|
}
|
|
|
|
auto RendererGL::GLErrorToString(GLenum err) -> std::string {
|
|
switch (err) {
|
|
case GL_NO_ERROR:
|
|
return "GL_NO_ERROR";
|
|
case GL_INVALID_ENUM:
|
|
return "GL_INVALID_ENUM";
|
|
case GL_INVALID_VALUE:
|
|
return "GL_INVALID_VALUE";
|
|
case GL_INVALID_OPERATION:
|
|
return "GL_INVALID_OPERATION";
|
|
case GL_OUT_OF_MEMORY:
|
|
return "GL_OUT_OF_MEMORY";
|
|
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
|
return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
|
default:
|
|
return std::to_string(err);
|
|
}
|
|
}
|
|
|
|
// Look for a gl extension prefixed by "GL_ARB", "GL_EXT", etc. Returns true
|
|
// if found.
|
|
static auto CheckGLExtension(const std::vector<std::string>& exts,
|
|
const char* ext) -> bool {
|
|
assert(strlen(ext) < 100);
|
|
const int variant_count{10};
|
|
char variants[variant_count][128];
|
|
int i = 0;
|
|
snprintf(variants[i], sizeof(variants[i]), "OES_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_OES_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_KHR_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_ARB_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_APPLE_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_EXT_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_NV_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_ATI_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_SGIS_%s", ext);
|
|
i++;
|
|
snprintf(variants[i], sizeof(variants[i]), "GL_IMG_%s", ext);
|
|
i++;
|
|
assert(i == variant_count);
|
|
|
|
for (auto&& ext : exts) {
|
|
for (int i = 0; i < variant_count; ++i) {
|
|
if (variants[i] == ext) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This is split into its own call because systems that load GL calls
|
|
// dynamically may want to run the check before trying to load said GL
|
|
// calls. It's better to die with a 'Your OpenGL is too old' error rather
|
|
// than a 'Could not load function foofDinglePlop2XZ'.
|
|
void RendererGL::CheckGLVersion() {
|
|
if (checked_gl_version_) {
|
|
return;
|
|
}
|
|
const char* version_str = (const char*)glGetString(GL_VERSION);
|
|
BA_PRECONDITION_FATAL(version_str);
|
|
|
|
// Do a rough check to make sure we're running 3 or newer of GL/GLES. This
|
|
// query should be available even on older versions which is why we do it
|
|
// before the GL_MAJOR_VERSION/GL_MINOR_VERSION business which is not.
|
|
if (gl_is_es()) {
|
|
// GL ES version strings start with 'OpenGL ES X' with X being version.
|
|
const char* prefix = "OpenGL ES ";
|
|
auto prefixlen = strlen(prefix);
|
|
BA_PRECONDITION_FATAL(!strncmp(version_str, prefix, prefixlen));
|
|
if (version_str[prefixlen] != '3') {
|
|
FatalError(
|
|
std::string("Your OpenGL ES version is too old (") + version_str
|
|
+ "). We require 3.0 or later. Try updating your graphics drivers.");
|
|
}
|
|
} else {
|
|
// Regular GL version strings start with numeric version.
|
|
|
|
if (version_str[0] != '3' && version_str[0] != '4') {
|
|
FatalError(
|
|
std::string("Your OpenGL version is too old (") + version_str
|
|
+ "). We require 3.0 or later. Try updating your graphics drivers.");
|
|
}
|
|
}
|
|
checked_gl_version_ = true;
|
|
}
|
|
|
|
void RendererGL::CheckGLCapabilities_() {
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
|
|
// Die if our overall GL version is too old.
|
|
CheckGLVersion();
|
|
|
|
const char* renderer = (const char*)glGetString(GL_RENDERER);
|
|
BA_PRECONDITION_FATAL(renderer);
|
|
const char* vendor = (const char*)glGetString(GL_VENDOR);
|
|
BA_PRECONDITION_FATAL(vendor);
|
|
const char* version_str = (const char*)glGetString(GL_VERSION);
|
|
BA_PRECONDITION_FATAL(version_str);
|
|
|
|
// Now fetch exact major/minor versions. This query requires version 3.0
|
|
// or newer which is why we checked overall version in CheckGLVersion()
|
|
// above.
|
|
glGetError(); // Clear any existing error so we don't die on it here.
|
|
glGetIntegerv(GL_MAJOR_VERSION, &gl_version_major_);
|
|
BA_PRECONDITION_FATAL(glGetError() == GL_NO_ERROR);
|
|
glGetIntegerv(GL_MINOR_VERSION, &gl_version_minor_);
|
|
BA_PRECONDITION_FATAL(glGetError() == GL_NO_ERROR);
|
|
|
|
const char* basestr;
|
|
if (gl_is_es()) {
|
|
basestr = "OpenGL ES";
|
|
} else {
|
|
basestr = "OpenGL";
|
|
}
|
|
|
|
Log(LogName::kBaGraphics, LogLevel::kInfo,
|
|
std::string("Using ") + basestr + " (vendor: " + vendor
|
|
+ ", renderer: " + renderer + ", version: " + version_str + ").");
|
|
|
|
// Build a vector of extensions. Newer GLs give us extensions as lists
|
|
// already, but on older ones we may need to break a single string apart
|
|
// ourself.
|
|
std::vector<std::string> extensions;
|
|
bool used_num_extensions{};
|
|
|
|
// Do the modern gl thing of looking through a list of extensions; not a
|
|
// single string.
|
|
if (auto num_extensions = GLGetIntOptional(GL_NUM_EXTENSIONS)) {
|
|
used_num_extensions = true;
|
|
extensions.reserve(*num_extensions);
|
|
for (int i = 0; i < num_extensions; ++i) {
|
|
const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
|
|
BA_PRECONDITION(extension);
|
|
extensions.push_back(extension);
|
|
}
|
|
} else {
|
|
Log(LogName::kBaGraphics, LogLevel::kWarning,
|
|
"Falling back on legacy GL_EXTENSIONS parsing.");
|
|
// Fall back on parsing the single giant string if need be.
|
|
// (Can probably kill this).
|
|
auto* ex = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
BA_PRECONDITION_FATAL(ex);
|
|
std::istringstream iss(ex);
|
|
extensions = {std::istream_iterator<std::string>(iss),
|
|
std::istream_iterator<std::string>()};
|
|
}
|
|
|
|
// On Android, look at the GL version and try to get gl3 funcs to
|
|
// determine if we're running ES3 or not.
|
|
#if BA_OSTYPE_ANDROID
|
|
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// Flag certain devices as 'speedy' - we use this to enable high/higher
|
|
// quality and whatnot (even in cases where ES3 isnt available).
|
|
|
|
// Let just consider ES 3.2 stuff speedy.
|
|
assert(gl_version_major() == 3);
|
|
is_speedy_android_device_ = gl_version_minor() >= 2;
|
|
|
|
is_adreno_ = (strstr(renderer, "Adreno") != nullptr);
|
|
|
|
#endif // BA_OSTYPE_ANDROID
|
|
|
|
std::list<TextureCompressionType> c_types;
|
|
assert(g_base->graphics);
|
|
if (CheckGLExtension(extensions, "texture_compression_s3tc")) {
|
|
c_types.push_back(TextureCompressionType::kS3TC);
|
|
}
|
|
|
|
// Limiting pvr support to iOS for the moment.
|
|
if (!g_buildconfig.ostype_android()) {
|
|
if (CheckGLExtension(extensions, "texture_compression_pvrtc")) {
|
|
c_types.push_back(TextureCompressionType::kPVR);
|
|
}
|
|
}
|
|
|
|
// Pretty much all Android devices should support ETC1.
|
|
if (CheckGLExtension(extensions, "compressed_ETC1_RGB8_texture")) {
|
|
c_types.push_back(TextureCompressionType::kETC1);
|
|
} else {
|
|
if (g_buildconfig.ostype_android()) {
|
|
Log(LogName::kBaGraphics, LogLevel::kError,
|
|
"Android device missing ETC1 support.");
|
|
}
|
|
}
|
|
|
|
// ETC2 is required for ES3 support (and OpenGL 4.4 or something once we
|
|
// eventually get there).
|
|
if (gl_is_es()) {
|
|
c_types.push_back(TextureCompressionType::kETC2);
|
|
}
|
|
|
|
// ASTC is generally available on newer mobile hardware.
|
|
if (CheckGLExtension(extensions, "texture_compression_astc_ldr")) {
|
|
c_types.push_back(TextureCompressionType::kASTC);
|
|
}
|
|
|
|
g_base->graphics_server->SetTextureCompressionTypes(c_types);
|
|
|
|
// Store the tex-compression type we support.
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// Anisotropic sampling is still an extension as of both GL 3 and ES 3, so
|
|
// we need to test for it.
|
|
anisotropic_support_ =
|
|
CheckGLExtension(extensions, "texture_filter_anisotropic");
|
|
if (anisotropic_support_) {
|
|
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy_);
|
|
}
|
|
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
if (gl_is_es()) {
|
|
// GL ES 3 has glInvalidateFramebuffer as part of the standard.
|
|
invalidate_framebuffer_support_ = true;
|
|
} else {
|
|
// It seems it's standard as of desktop GL 4.3 so we could probably
|
|
// use it selectively if we wanted.
|
|
invalidate_framebuffer_support_ = false;
|
|
}
|
|
|
|
combined_texture_image_unit_count_ =
|
|
GLGetInt(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
|
|
|
|
// If we're running ES3, ask about our max multisample counts and whether
|
|
// we can enable MSAA.
|
|
msaa_max_samples_rgb565_ = msaa_max_samples_rgb8_ = 0; // start pessimistic
|
|
|
|
bool have_gl_get_internal_format_iv{};
|
|
if (gl_is_es()) {
|
|
// This is available on ES 3.
|
|
have_gl_get_internal_format_iv = true;
|
|
} else {
|
|
// This is available on GL 4.2 or newer.
|
|
if (gl_version_major() == 4 && gl_version_minor() >= 2) {
|
|
have_gl_get_internal_format_iv = true;
|
|
}
|
|
}
|
|
|
|
if (have_gl_get_internal_format_iv) {
|
|
GLint count;
|
|
glGetInternalformativ(GL_RENDERBUFFER, GL_RGB565, GL_NUM_SAMPLE_COUNTS, 1,
|
|
&count);
|
|
if (count > 0) {
|
|
std::vector<GLint> samples;
|
|
samples.resize(static_cast<size_t>(static_cast<size_t>(count)));
|
|
glGetInternalformativ(GL_RENDERBUFFER, GL_RGB565, GL_SAMPLES, count,
|
|
&samples[0]);
|
|
msaa_max_samples_rgb565_ = samples[0];
|
|
} else {
|
|
BA_LOG_ONCE(LogName::kBaGraphics, LogLevel::kError,
|
|
"Got 0 samplecounts for RGB565");
|
|
msaa_max_samples_rgb565_ = 0;
|
|
}
|
|
|
|
// RGB8 max multisamples.
|
|
glGetInternalformativ(GL_RENDERBUFFER, GL_RGB8, GL_NUM_SAMPLE_COUNTS, 1,
|
|
&count);
|
|
if (count > 0) {
|
|
std::vector<GLint> samples;
|
|
samples.resize(static_cast<size_t>(count));
|
|
glGetInternalformativ(GL_RENDERBUFFER, GL_RGB8, GL_SAMPLES, count,
|
|
&samples[0]);
|
|
msaa_max_samples_rgb8_ = samples[0];
|
|
} else {
|
|
BA_LOG_ONCE(LogName::kBaGraphics, LogLevel::kError,
|
|
"Got 0 samplecounts for RGB8");
|
|
msaa_max_samples_rgb8_ = 0;
|
|
}
|
|
} else {
|
|
// For older GL (which includes all Macs) it sounds like this is the way
|
|
// to query max samples?.. but I don't know for sure if this applies to
|
|
// renderbuffer targets or just the default drawable. Will it ever be
|
|
// different?
|
|
auto max_samples = GLGetIntOptional(GL_MAX_SAMPLES);
|
|
if (max_samples.has_value()) {
|
|
msaa_max_samples_rgb565_ = msaa_max_samples_rgb8_ = *max_samples;
|
|
}
|
|
}
|
|
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
first_extension_check_ = false;
|
|
}
|
|
|
|
auto RendererGL::GetMSAASamplesForFramebuffer_(int width, int height) -> int {
|
|
if (g_buildconfig.ostype_android()) {
|
|
// We currently aim for 4 up to 800 height and 2 beyond that.
|
|
if (height > 800) {
|
|
return 2;
|
|
} else {
|
|
return 4;
|
|
}
|
|
} else {
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
void RendererGL::UpdateMSAAEnabled_() {
|
|
if (g_buildconfig.ostype_macos()) {
|
|
// Let's go ahead and flip this on for Apple Silicon Macs.
|
|
#if __aarch64__
|
|
enable_msaa_ = true;
|
|
#else
|
|
enable_msaa_ = false;
|
|
#endif
|
|
} else if (g_buildconfig.rift_build()) {
|
|
if (msaa_max_samples_rgb8_ > 0) {
|
|
enable_msaa_ = true;
|
|
} else {
|
|
enable_msaa_ = false;
|
|
}
|
|
} else if (g_buildconfig.ostype_android()) {
|
|
// lets allow full 1080p msaa with newer stuff..
|
|
int max_msaa_res = is_tegra_k1_ ? 1200 : 800;
|
|
|
|
// To start, see if it looks like we support msaa on paper.
|
|
enable_msaa_ =
|
|
((screen_render_target()->physical_height()
|
|
<= static_cast<float>(max_msaa_res))
|
|
&& (msaa_max_samples_rgb8_ > 0) && (msaa_max_samples_rgb565_ > 0));
|
|
|
|
// Ok, lets be careful here; msaa blitting/etc seems to be particular in
|
|
// terms of supported formats/etc so let's only enable it on
|
|
// explicitly-tested hardware for now.
|
|
if (!is_tegra_4_ && !is_tegra_k1_ && !is_recent_adreno_) {
|
|
enable_msaa_ = false;
|
|
}
|
|
} else {
|
|
enable_msaa_ = false;
|
|
}
|
|
}
|
|
|
|
auto RendererGL::IsMSAAEnabled() const -> bool { return enable_msaa_; }
|
|
|
|
auto RendererGL::GetGLTextureFormat(TextureFormat f) -> GLenum {
|
|
switch (f) {
|
|
case TextureFormat::kDXT1:
|
|
return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
|
break;
|
|
case TextureFormat::kDXT5:
|
|
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
break;
|
|
case TextureFormat::kPVR2:
|
|
return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
|
|
break;
|
|
case TextureFormat::kPVR4:
|
|
return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
|
|
break;
|
|
case TextureFormat::kETC1:
|
|
return GL_ETC1_RGB8_OES;
|
|
break;
|
|
case TextureFormat::kETC2_RGB:
|
|
return GL_COMPRESSED_RGB8_ETC2;
|
|
break;
|
|
case TextureFormat::kETC2_RGBA:
|
|
return GL_COMPRESSED_RGBA8_ETC2_EAC;
|
|
break;
|
|
default:
|
|
throw Exception("Invalid TextureFormat: "
|
|
+ std::to_string(static_cast<int>(f)));
|
|
}
|
|
}
|
|
|
|
void RendererGL::SetViewport_(GLint x, GLint y, GLsizei width, GLsizei height) {
|
|
if (x != viewport_x_ || y != viewport_y_ || width != viewport_width_
|
|
|| height != viewport_height_) {
|
|
viewport_x_ = x;
|
|
viewport_y_ = y;
|
|
viewport_width_ = width;
|
|
viewport_height_ = height;
|
|
glViewport(viewport_x_, viewport_y_, viewport_width_, viewport_height_);
|
|
}
|
|
}
|
|
|
|
void RendererGL::BindTextureUnit(uint32_t tex_unit) {
|
|
assert(tex_unit >= 0 && tex_unit < kMaxGLTexUnitsUsed);
|
|
if (active_tex_unit_ != -1) {
|
|
// Make sure our internal state stays correct.
|
|
assert(GLGetInt(GL_ACTIVE_TEXTURE) == GL_TEXTURE0 + active_tex_unit_);
|
|
}
|
|
if (active_tex_unit_ != tex_unit) {
|
|
active_tex_unit_ = tex_unit;
|
|
glActiveTexture(GL_TEXTURE0 + active_tex_unit_);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
} else {
|
|
}
|
|
}
|
|
|
|
auto RendererGL::GLGetInt(GLenum name) -> int {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
|
|
// Clear any error coming in; don't want to fail for something that's not
|
|
// our problem.
|
|
if (g_buildconfig.debug_build()) {
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
} else {
|
|
glGetError();
|
|
}
|
|
GLint val;
|
|
glGetIntegerv(name, &val);
|
|
if (glGetError() != GL_NO_ERROR) {
|
|
FatalError("Unable to fetch GL int " + std::to_string(name));
|
|
}
|
|
return val;
|
|
}
|
|
|
|
auto RendererGL::GLGetIntOptional(GLenum name) -> std::optional<int> {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
|
|
// Clear any error coming in; don't want to fail for something that's not
|
|
// our problem.
|
|
if (g_buildconfig.debug_build()) {
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
} else {
|
|
glGetError();
|
|
}
|
|
GLint val;
|
|
glGetIntegerv(name, &val);
|
|
if (glGetError() != GL_NO_ERROR) {
|
|
return {};
|
|
}
|
|
return val;
|
|
}
|
|
|
|
void RendererGL::BindFramebuffer(GLuint fb) {
|
|
if (active_framebuffer_ != fb) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fb);
|
|
active_framebuffer_ = fb;
|
|
} else {
|
|
assert(GLGetInt(GL_FRAMEBUFFER_BINDING) == fb);
|
|
}
|
|
}
|
|
|
|
void RendererGL::BindArrayBuffer(GLuint b) {
|
|
if (active_array_buffer_ != -1) {
|
|
// Make sure our internal state stays correct.
|
|
assert(GLGetInt(GL_ARRAY_BUFFER_BINDING) == active_array_buffer_);
|
|
}
|
|
if (active_array_buffer_ != b) {
|
|
glBindBuffer(GL_ARRAY_BUFFER, b);
|
|
active_array_buffer_ = b;
|
|
}
|
|
}
|
|
|
|
void RendererGL::BindTexture_(GLuint type, const TextureAsset* t,
|
|
GLuint tex_unit) {
|
|
if (t) {
|
|
auto data = static_cast_check_type<TextureDataGL*>(t->renderer_data());
|
|
BindTexture_(type, data->GetTexture(), tex_unit);
|
|
} else {
|
|
// Fallback to noise.
|
|
BindTexture_(type, random_tex_, tex_unit);
|
|
}
|
|
}
|
|
|
|
void RendererGL::BindTexture_(GLuint type, GLuint tex, GLuint tex_unit) {
|
|
switch (type) {
|
|
case GL_TEXTURE_2D: {
|
|
// Make sure our internal state stays correct.
|
|
if (g_buildconfig.debug_build()) {
|
|
if (bound_textures_2d_[tex_unit] != -1) {
|
|
BindTextureUnit(tex_unit);
|
|
assert(GLGetInt(GL_TEXTURE_BINDING_2D)
|
|
== bound_textures_2d_[tex_unit]);
|
|
}
|
|
}
|
|
if (tex != bound_textures_2d_[tex_unit]) {
|
|
BindTextureUnit(tex_unit);
|
|
glBindTexture(type, tex);
|
|
bound_textures_2d_[tex_unit] = tex;
|
|
}
|
|
break;
|
|
}
|
|
case GL_TEXTURE_CUBE_MAP: {
|
|
// Make sure our internal state stays correct.
|
|
if (g_buildconfig.debug_build()) {
|
|
if (bound_textures_cube_map_[tex_unit] != -1) {
|
|
BindTextureUnit(tex_unit);
|
|
assert(GLGetInt(GL_TEXTURE_BINDING_CUBE_MAP)
|
|
== bound_textures_cube_map_[tex_unit]);
|
|
}
|
|
}
|
|
if (tex != bound_textures_cube_map_[tex_unit]) {
|
|
BindTextureUnit(tex_unit);
|
|
glBindTexture(type, tex);
|
|
bound_textures_cube_map_[tex_unit] = tex;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
throw Exception();
|
|
}
|
|
}
|
|
|
|
void RendererGL::CheckFunkyDepthIssue_() {
|
|
if (funky_depth_issue_set_) {
|
|
return;
|
|
}
|
|
|
|
// Note: this test fails for some reason on some Broadcom VideoCore and
|
|
// older NVidia chips (tegra 2?) ...so lets limit testing to adreno chips
|
|
// since that's the only place the problem is known to happen.
|
|
if (!is_adreno_) {
|
|
// if (!is_adreno_ || !supports_depth_textures_) {
|
|
funky_depth_issue_set_ = true;
|
|
funky_depth_issue_ = false;
|
|
return;
|
|
}
|
|
|
|
// On some adreno chips, depth buffer values are always returned
|
|
// in a 0-1 range in shaders even if a depth range is set; everywhere
|
|
// else they return that depth range.
|
|
// To test for this, we can create a temp buffer, clear it, set a depth range,
|
|
|
|
Object::Ref<RenderTargetGL> test_rt1;
|
|
Object::Ref<RenderTargetGL> test_rt2;
|
|
|
|
test_rt1 = Object::New<RenderTargetGL>(this, 32, 32, true, true, true, true,
|
|
false, false, false);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
test_rt2 = Object::New<RenderTargetGL>(this, 32, 32, true, false, true, false,
|
|
false, false, false);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// This screws up some qualcomm chips.
|
|
SetDepthRange(0.0f, 0.5f);
|
|
|
|
// Draw a flat color plane into our first render target.
|
|
SetDepthWriting(true);
|
|
SetDepthTesting(true);
|
|
SetBlend(false);
|
|
SetDoubleSided_(false);
|
|
test_rt1->DrawBegin(true, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
ProgramSimpleGL* p = simple_color_prog_;
|
|
p->Bind();
|
|
p->SetColor(1, 0, 1);
|
|
g_base->graphics_server->ModelViewReset();
|
|
g_base->graphics_server->SetOrthoProjection(-1, 1, -1, 1, -1, 1);
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// Now draw into a second buffer the difference between the depth tex
|
|
// lookup and the gl frag depth.
|
|
SetDepthWriting(false);
|
|
SetDepthTesting(false);
|
|
SetBlend(false);
|
|
SetDoubleSided_(false);
|
|
test_rt2->DrawBegin(false, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
p = simple_tex_dtest_prog_;
|
|
p->Bind();
|
|
g_base->graphics_server->ModelViewReset();
|
|
g_base->graphics_server->SetOrthoProjection(-1, 1, -1, 1, -1, 1);
|
|
p->SetColorTexture(test_rt1->framebuffer()->depth_texture());
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// Now sample a pixel from our render-target. If the depths matched, the
|
|
// value will be 0; otherwise it'll be 30 or so (allow a bit of leeway to
|
|
// account for dithering/etc.).
|
|
uint8_t buffer[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
glReadPixels(0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
|
|
|
|
// Sample 4 pixels to reduce effects of dithering.
|
|
funky_depth_issue_ =
|
|
((buffer[0] + buffer[4] + buffer[8] + buffer[12]) / 4 >= 15);
|
|
funky_depth_issue_set_ = true;
|
|
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
|
|
void RendererGL::PushGroupMarker(const char* label) {
|
|
BA_GL_PUSH_GROUP_MARKER(label);
|
|
}
|
|
void RendererGL::PopGroupMarker() { BA_GL_POP_GROUP_MARKER(); }
|
|
|
|
void RendererGL::InvalidateFramebuffer(bool color, bool depth,
|
|
bool target_read_framebuffer) {
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// Currently this is ES only for us.
|
|
#if BA_OPENGL_IS_ES
|
|
|
|
if (invalidate_framebuffer_support()) {
|
|
GLenum attachments[5];
|
|
// Need to use different flags for the main framebuffer.
|
|
int count = 0;
|
|
if (active_framebuffer_ == 0 && !target_read_framebuffer) {
|
|
if (color) {
|
|
attachments[count++] = GL_COLOR;
|
|
}
|
|
if (depth) {
|
|
attachments[count++] = GL_DEPTH;
|
|
}
|
|
} else {
|
|
if (color) {
|
|
attachments[count++] = GL_COLOR_ATTACHMENT0;
|
|
}
|
|
if (depth) {
|
|
attachments[count++] = GL_DEPTH_ATTACHMENT;
|
|
}
|
|
|
|
glInvalidateFramebuffer(
|
|
target_read_framebuffer ? GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER, count,
|
|
attachments);
|
|
}
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
#else
|
|
// Make noise if we should be doing this here too at some point.
|
|
assert(!invalidate_framebuffer_support());
|
|
#endif // BA_OPENGL_IS_ES
|
|
}
|
|
|
|
RendererGL::~RendererGL() {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
printf("FIXME: need to unload renderer on destroy.\n");
|
|
// Unload();
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
|
|
void RendererGL::UseProgram_(ProgramGL* p) {
|
|
if (p != current_program_) {
|
|
glUseProgram(p->program());
|
|
current_program_ = p;
|
|
}
|
|
}
|
|
|
|
void RendererGL::SyncGLState_() {
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
#if BA_RIFT_BUILD
|
|
if (g_core->vr_mode()) {
|
|
glFrontFace(GL_CCW);
|
|
}
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
#endif // BA_RIFT_BUILD
|
|
|
|
active_tex_unit_ = -1; // force a set next time
|
|
active_framebuffer_ = -1; // ditto
|
|
active_array_buffer_ = -1; // ditto
|
|
for (int i = 0; i < kMaxGLTexUnitsUsed; i++) {
|
|
bound_textures_2d_[i] = -1; // ditto
|
|
bound_textures_cube_map_[i] = -1; // ditto
|
|
}
|
|
glUseProgram(0);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
current_program_ = nullptr;
|
|
current_vertex_array_ = 0;
|
|
|
|
glBindVertexArray(0);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
// Wack these out so the next call will definitely call glViewport.
|
|
viewport_x_ = -9999;
|
|
viewport_y_ = -9999;
|
|
viewport_width_ = -9999;
|
|
viewport_height_ = -9999;
|
|
|
|
glDisable(GL_BLEND);
|
|
blend_ = false;
|
|
|
|
// Currently we only ever write to an alpha buffer for our vr flat overlay
|
|
// texture, and in that case we need alpha to accumulate; not get
|
|
// overwritten. could probably enable this everywhere but I don't know if
|
|
// it's supported on all hardware or slower.
|
|
if (g_core->vr_mode()) {
|
|
#if BA_OSTYPE_WINDOWS
|
|
if (glBlendFuncSeparate == nullptr) {
|
|
FatalError(
|
|
"VR mode is not supported by your GPU (no glBlendFuncSeparate); Try "
|
|
"updating your drivers?...");
|
|
}
|
|
#endif
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
|
|
GL_ONE_MINUS_SRC_ALPHA);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
} else {
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
blend_premult_ = false;
|
|
glEnable(GL_CULL_FACE);
|
|
glCullFace(GL_BACK);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
double_sided_ = false;
|
|
draw_front_ = true;
|
|
glDisable(GL_DEPTH_TEST);
|
|
depth_testing_enabled_ = false;
|
|
glDepthMask(static_cast<GLboolean>(true));
|
|
depth_writing_enabled_ = true;
|
|
draw_at_equal_depth_ = false;
|
|
glDepthFunc(GL_LESS);
|
|
depth_range_min_ = 0.0f;
|
|
depth_range_max_ = 1.0f;
|
|
glDepthRange(depth_range_min_, depth_range_max_);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
|
|
#define GET_MESH_DATA(TYPE, VAR) \
|
|
auto* VAR = static_cast<TYPE*>(mesh_data->renderer_data()); \
|
|
assert(VAR&& VAR == dynamic_cast<TYPE*>(mesh_data->renderer_data()))
|
|
|
|
#define GET_INDEX_BUFFER() \
|
|
assert(buffer != buffers.end()); \
|
|
assert(index_size != index_sizes.end()); \
|
|
MeshIndexBuffer16* indices16{nullptr}; \
|
|
MeshIndexBuffer32* indices32{nullptr}; \
|
|
assert(*index_size == 4 || *index_size == 2); \
|
|
bool use_indices32 = (*index_size == 4); \
|
|
if (use_indices32) { \
|
|
indices32 = static_cast<MeshIndexBuffer32*>(buffer->Get()); \
|
|
assert(indices32&& indices32 \
|
|
== dynamic_cast<MeshIndexBuffer32*>(buffer->Get())); \
|
|
} else { \
|
|
indices16 = static_cast<MeshIndexBuffer16*>(buffer->Get()); \
|
|
assert(indices16&& indices16 \
|
|
== dynamic_cast<MeshIndexBuffer16*>(buffer->Get())); \
|
|
} \
|
|
index_size++; \
|
|
buffer++
|
|
|
|
#define GET_BUFFER(TYPE, VAR) \
|
|
assert(buffer != buffers.end()); \
|
|
auto* VAR = static_cast<TYPE*>(buffer->Get()); \
|
|
assert(VAR&& VAR == dynamic_cast<TYPE*>(buffer->Get())); \
|
|
buffer++
|
|
|
|
// Takes all latest mesh data from the client side and applies it to our gl
|
|
// implementations.
|
|
void RendererGL::UpdateMeshes(
|
|
const std::vector<Object::Ref<MeshDataClientHandle> >& meshes,
|
|
const std::vector<int8_t>& index_sizes,
|
|
const std::vector<Object::Ref<MeshBufferBase> >& buffers) {
|
|
auto index_size = index_sizes.begin();
|
|
auto buffer = buffers.begin();
|
|
for (auto&& mesh : meshes) {
|
|
// For each mesh, plug in the latest and greatest buffers it should be
|
|
// using.
|
|
MeshData* mesh_data = mesh->mesh_data;
|
|
switch (mesh_data->type()) {
|
|
case MeshDataType::kIndexedSimpleSplit: {
|
|
GET_MESH_DATA(MeshDataSimpleSplitGL, m);
|
|
GET_INDEX_BUFFER();
|
|
GET_BUFFER(MeshBuffer<VertexSimpleSplitStatic>, static_data);
|
|
GET_BUFFER(MeshBuffer<VertexSimpleSplitDynamic>, dynamic_data);
|
|
if (use_indices32) {
|
|
m->SetIndexData(indices32);
|
|
} else {
|
|
m->SetIndexData(indices16);
|
|
}
|
|
m->SetStaticData(static_data);
|
|
m->SetDynamicData(dynamic_data);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedObjectSplit: {
|
|
GET_MESH_DATA(MeshDataObjectSplitGL, m);
|
|
GET_INDEX_BUFFER();
|
|
GET_BUFFER(MeshBuffer<VertexObjectSplitStatic>, static_data);
|
|
GET_BUFFER(MeshBuffer<VertexObjectSplitDynamic>, dynamic_data);
|
|
if (use_indices32) {
|
|
m->SetIndexData(indices32);
|
|
} else {
|
|
m->SetIndexData(indices16);
|
|
}
|
|
m->SetStaticData(static_data);
|
|
m->SetDynamicData(dynamic_data);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedSimpleFull: {
|
|
GET_MESH_DATA(MeshDataSimpleFullGL, m);
|
|
GET_INDEX_BUFFER();
|
|
GET_BUFFER(MeshBuffer<VertexSimpleFull>, data);
|
|
if (use_indices32) {
|
|
m->SetIndexData(indices32);
|
|
} else {
|
|
m->SetIndexData(indices16);
|
|
}
|
|
m->SetData(data);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedDualTextureFull: {
|
|
GET_MESH_DATA(MeshDataDualTextureFullGL, m);
|
|
GET_INDEX_BUFFER();
|
|
GET_BUFFER(MeshBuffer<VertexDualTextureFull>, data);
|
|
if (use_indices32) {
|
|
m->SetIndexData(indices32);
|
|
} else {
|
|
m->SetIndexData(indices16);
|
|
}
|
|
m->SetData(data);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedSmokeFull: {
|
|
GET_MESH_DATA(MeshDataSmokeFullGL, m);
|
|
GET_INDEX_BUFFER();
|
|
GET_BUFFER(MeshBuffer<VertexSmokeFull>, data);
|
|
if (use_indices32) {
|
|
m->SetIndexData(indices32);
|
|
} else {
|
|
m->SetIndexData(indices16);
|
|
}
|
|
m->SetData(data);
|
|
break;
|
|
}
|
|
case MeshDataType::kSprite: {
|
|
GET_MESH_DATA(MeshDataSpriteGL, m);
|
|
GET_INDEX_BUFFER();
|
|
GET_BUFFER(MeshBuffer<VertexSprite>, data);
|
|
if (use_indices32) {
|
|
m->SetIndexData(indices32);
|
|
} else {
|
|
m->SetIndexData(indices16);
|
|
}
|
|
m->SetData(data);
|
|
break;
|
|
}
|
|
default:
|
|
throw Exception("Invalid meshdata type: "
|
|
+ std::to_string(static_cast<int>(mesh_data->type())));
|
|
}
|
|
}
|
|
// We should have gone through all lists exactly.
|
|
assert(index_size == index_sizes.end());
|
|
assert(buffer == buffers.end());
|
|
}
|
|
#undef GET_MESH_DATA
|
|
#undef GET_BUFFER
|
|
#undef GET_INDEX_BUFFER
|
|
|
|
void RendererGL::StandardPostProcessSetup_(ProgramPostProcessGL* p,
|
|
const RenderPass& pass) {
|
|
auto* cam_target = static_cast<RenderTargetGL*>(camera_render_target());
|
|
assert(cam_target
|
|
&& dynamic_cast<RenderTargetGL*>(camera_render_target())
|
|
== cam_target);
|
|
RenderPass* beauty_pass = pass.frame_def()->beauty_pass();
|
|
assert(beauty_pass);
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
p->Bind();
|
|
p->SetColorTexture(cam_target->framebuffer()->texture());
|
|
if (p->UsesSlightBlurredTex()) {
|
|
p->SetColorSlightBlurredTexture(blur_buffers_[0]->texture());
|
|
}
|
|
if (blur_buffers_.size() > 1) {
|
|
if (p->UsesBlurredTexture()) {
|
|
p->SetColorBlurredTexture(blur_buffers_[1]->texture());
|
|
}
|
|
p->SetColorBlurredMoreTexture(
|
|
blur_buffers_[blur_buffers_.size() - 1]->texture());
|
|
} else {
|
|
if (p->UsesBlurredTexture()) {
|
|
p->SetColorBlurredTexture(blur_buffers_[0]->texture());
|
|
}
|
|
p->SetColorBlurredMoreTexture(blur_buffers_[0]->texture());
|
|
}
|
|
p->SetDepthTexture(cam_target->framebuffer()->depth_texture());
|
|
float dof_near_smoothed = this->dof_near_smoothed();
|
|
float dof_far_smoothed = this->dof_far_smoothed();
|
|
|
|
// FIXME: These sort of fudge-factors don't belong here in the renderer.
|
|
if (pass.frame_def()->orbiting()) {
|
|
p->SetDepthOfFieldRanges(
|
|
GetZBufferValue(beauty_pass, 0.80f * dof_near_smoothed),
|
|
GetZBufferValue(beauty_pass, 0.91f * dof_near_smoothed),
|
|
GetZBufferValue(beauty_pass, 1.01f * dof_far_smoothed),
|
|
GetZBufferValue(beauty_pass, 1.10f * dof_far_smoothed));
|
|
} else {
|
|
p->SetDepthOfFieldRanges(
|
|
GetZBufferValue(beauty_pass, 0.93f * dof_near_smoothed),
|
|
GetZBufferValue(beauty_pass, 0.99f * dof_near_smoothed),
|
|
GetZBufferValue(beauty_pass, 1.03f * dof_far_smoothed),
|
|
GetZBufferValue(beauty_pass, 1.09f * dof_far_smoothed));
|
|
}
|
|
}
|
|
|
|
void RendererGL::ProcessRenderCommandBuffer(RenderCommandBuffer* buffer,
|
|
const RenderPass& pass,
|
|
RenderTarget* render_target) {
|
|
buffer->ReadBegin();
|
|
RenderCommandBuffer::Command cmd;
|
|
while ((cmd = buffer->GetCommand()) != RenderCommandBuffer::Command::kEnd) {
|
|
switch (cmd) {
|
|
case RenderCommandBuffer::Command::kEnd:
|
|
break;
|
|
case RenderCommandBuffer::Command::kShader: {
|
|
auto shader = static_cast<ShadingType>(buffer->GetInt());
|
|
switch (shader) {
|
|
case ShadingType::kSimpleColor: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
ProgramSimpleGL* p = simple_color_prog_;
|
|
p->Bind();
|
|
float r, g, b;
|
|
buffer->GetFloats(&r, &g, &b);
|
|
p->SetColor(r, g, b);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleColorTransparent: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
ProgramSimpleGL* p = simple_color_prog_;
|
|
p->Bind();
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
p->SetColor(r, g, b, a);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleColorTransparentDoubleSided: {
|
|
SetDoubleSided_(true);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
ProgramSimpleGL* p = simple_color_prog_;
|
|
p->Bind();
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
p->SetColor(r, g, b, a);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTexture: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
ProgramSimpleGL* p = simple_tex_prog_;
|
|
p->Bind();
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparent: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
ProgramSimpleGL* p = simple_tex_mod_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransFlatness: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, flatness;
|
|
buffer->GetFloats(&r, &g, &b, &a, &flatness);
|
|
ProgramSimpleGL* p = simple_tex_mod_flatness_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetFlatness(flatness);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparentShadow: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, shadow_offset_x, shadow_offset_y, shadow_blur,
|
|
shadow_opacity;
|
|
buffer->GetFloats(&r, &g, &b, &a, &shadow_offset_x,
|
|
&shadow_offset_y, &shadow_blur, &shadow_opacity);
|
|
ProgramSimpleGL* p = simple_tex_mod_shadow_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
const TextureAsset* t = buffer->GetTexture();
|
|
const TextureAsset* t_mask = buffer->GetTexture();
|
|
p->SetColorTexture(t);
|
|
// If this isn't a full-res texture, ramp down the blurring we
|
|
// do.
|
|
p->SetShadow(shadow_offset_x, shadow_offset_y,
|
|
std::max(0.0f, shadow_blur), shadow_opacity);
|
|
p->SetMaskUV2Texture(t_mask);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTexModulatedTransShadowFlatness: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, shadow_offset_x, shadow_offset_y, shadow_blur,
|
|
shadow_opacity, flatness;
|
|
buffer->GetFloats(&r, &g, &b, &a, &shadow_offset_x,
|
|
&shadow_offset_y, &shadow_blur, &shadow_opacity,
|
|
&flatness);
|
|
ProgramSimpleGL* p = simple_tex_mod_shadow_flatness_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
const TextureAsset* t = buffer->GetTexture();
|
|
const TextureAsset* t_mask = buffer->GetTexture();
|
|
p->SetColorTexture(t);
|
|
// If this isn't a full-res texture, ramp down the blurring we
|
|
// do.
|
|
p->SetShadow(shadow_offset_x, shadow_offset_y,
|
|
std::max(0.0f, shadow_blur), shadow_opacity);
|
|
p->SetMaskUV2Texture(t_mask);
|
|
p->SetFlatness(flatness);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparentGlow: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, glow_amount, glow_blur;
|
|
buffer->GetFloats(&r, &g, &b, &a, &glow_amount, &glow_blur);
|
|
ProgramSimpleGL* p = simple_tex_mod_glow_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
const TextureAsset* t = buffer->GetTexture();
|
|
p->SetColorTexture(t);
|
|
|
|
// Glow.
|
|
p->SetGlow(glow_amount, std::max(0.0f, glow_blur));
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparentGlowMaskUV2: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, glow_amount, glow_blur;
|
|
buffer->GetFloats(&r, &g, &b, &a, &glow_amount, &glow_blur);
|
|
ProgramSimpleGL* p = simple_tex_mod_glow_maskuv2_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
const TextureAsset* t = buffer->GetTexture();
|
|
p->SetColorTexture(t);
|
|
const TextureAsset* t_mask = buffer->GetTexture();
|
|
p->SetMaskUV2Texture(t_mask);
|
|
// Glow.
|
|
p->SetGlow(glow_amount, std::max(0.0f, glow_blur));
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparentDoubleSided: {
|
|
SetDoubleSided_(true);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
ProgramSimpleGL* p = simple_tex_mod_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulated: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
float r, g, b;
|
|
buffer->GetFloats(&r, &g, &b);
|
|
ProgramSimpleGL* p = simple_tex_mod_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedColorized: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
float r, g, b, colorize_r, colorize_g, colorize_b;
|
|
buffer->GetFloats(&r, &g, &b, &colorize_r, &colorize_g,
|
|
&colorize_b);
|
|
ProgramSimpleGL* p = simple_tex_mod_colorized_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedColorized2: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
float r, g, b, colorize_r, colorize_g, colorize_b, colorize2_r,
|
|
colorize2_g, colorize2_b;
|
|
buffer->GetFloats(&r, &g, &b, &colorize_r, &colorize_g, &colorize_b,
|
|
&colorize2_r, &colorize2_g, &colorize2_b);
|
|
ProgramSimpleGL* p = simple_tex_mod_colorized2_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorize2Color(colorize2_r, colorize2_g, colorize2_b);
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedColorized2Masked: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
float r, g, b, a, colorize_r, colorize_g, colorize_b, colorize2_r,
|
|
colorize2_g, colorize2_b;
|
|
buffer->GetFloats(&r, &g, &b, &a, &colorize_r, &colorize_g,
|
|
&colorize_b, &colorize2_r, &colorize2_g,
|
|
&colorize2_b);
|
|
ProgramSimpleGL* p = simple_tex_mod_colorized2_masked_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorize2Color(colorize2_r, colorize2_g, colorize2_b);
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetMaskTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparentColorized: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, colorize_r, colorize_g, colorize_b;
|
|
buffer->GetFloats(&r, &g, &b, &a, &colorize_r, &colorize_g,
|
|
&colorize_b);
|
|
ProgramSimpleGL* p = simple_tex_mod_colorized_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSimpleTextureModulatedTransparentColorized2: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, colorize_r, colorize_g, colorize_b, colorize2_r,
|
|
colorize2_g, colorize2_b;
|
|
buffer->GetFloats(&r, &g, &b, &a, &colorize_r, &colorize_g,
|
|
&colorize_b, &colorize2_r, &colorize2_g,
|
|
&colorize2_b);
|
|
ProgramSimpleGL* p = simple_tex_mod_colorized2_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorize2Color(colorize2_r, colorize2_g, colorize2_b);
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::
|
|
kSimpleTextureModulatedTransparentColorized2Masked: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, colorize_r, colorize_g, colorize_b, colorize2_r,
|
|
colorize2_g, colorize2_b;
|
|
buffer->GetFloats(&r, &g, &b, &a, &colorize_r, &colorize_g,
|
|
&colorize_b, &colorize2_r, &colorize2_g,
|
|
&colorize2_b);
|
|
ProgramSimpleGL* p = simple_tex_mod_colorized2_masked_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorize2Color(colorize2_r, colorize2_g, colorize2_b);
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetMaskTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kObject: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
float r, g, b;
|
|
buffer->GetFloats(&r, &g, &b);
|
|
ProgramObjectGL* p = obj_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
break;
|
|
}
|
|
case ShadingType::kSmoke: {
|
|
SetDoubleSided_(true);
|
|
SetBlend(true);
|
|
SetBlendPremult(true);
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
ProgramSmokeGL* p = smoke_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kSmokeOverlay: {
|
|
SetDoubleSided_(true);
|
|
SetBlend(true);
|
|
SetBlendPremult(true);
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
ProgramSmokeGL* p = smoke_overlay_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetDepthTexture(
|
|
static_cast<RenderTargetGL*>(camera_render_target())
|
|
->framebuffer()
|
|
->depth_texture());
|
|
p->SetBlurTexture(
|
|
blur_buffers_[blur_buffers_.size() - 1]->texture());
|
|
break;
|
|
}
|
|
case ShadingType::kPostProcessNormalDistort: {
|
|
float distort = buffer->GetFloat();
|
|
ProgramPostProcessGL* p = postprocess_distort_prog_;
|
|
StandardPostProcessSetup_(p, pass);
|
|
p->SetDistort(distort);
|
|
break;
|
|
}
|
|
case ShadingType::kPostProcess: {
|
|
ProgramPostProcessGL* p = postprocess_prog_;
|
|
StandardPostProcessSetup_(p, pass);
|
|
break;
|
|
}
|
|
case ShadingType::kPostProcessEyes: {
|
|
assert(postprocess_eyes_prog_);
|
|
ProgramPostProcessGL* p = postprocess_eyes_prog_;
|
|
StandardPostProcessSetup_(p, pass);
|
|
break;
|
|
}
|
|
case ShadingType::kSprite: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(true);
|
|
SetBlendPremult(true);
|
|
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
bool overlay = static_cast<bool>(buffer->GetInt());
|
|
bool cam_aligned = static_cast<bool>(buffer->GetInt());
|
|
|
|
ProgramSpriteGL* p;
|
|
if (cam_aligned) {
|
|
if (overlay) {
|
|
p = sprite_camalign_overlay_prog_;
|
|
} else {
|
|
p = sprite_camalign_prog_;
|
|
}
|
|
} else {
|
|
assert(!overlay); // Unsupported combo.
|
|
p = sprite_prog_;
|
|
}
|
|
p->Bind();
|
|
if (overlay) {
|
|
p->SetDepthTexture(
|
|
static_cast<RenderTargetGL*>(camera_render_target())
|
|
->framebuffer()
|
|
->depth_texture());
|
|
}
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
break;
|
|
}
|
|
case ShadingType::kObjectTransparent: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
ProgramObjectGL* p = obj_transparent_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectLightShadow: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
int world_space = buffer->GetInt();
|
|
float r, g, b;
|
|
buffer->GetFloats(&r, &g, &b);
|
|
ProgramObjectGL* p = world_space ? obj_lightshad_worldspace_prog_
|
|
: obj_lightshad_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectLightShadowTransparent: {
|
|
SetDoubleSided_(false);
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
ProgramObjectGL* p = obj_lightshad_transparent_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadow: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
int world_space = buffer->GetInt();
|
|
float r, g, b, reflect_r, reflect_g, reflect_b;
|
|
buffer->GetFloats(&r, &g, &b, &reflect_r, &reflect_g, &reflect_b);
|
|
ProgramObjectGL* p = world_space
|
|
? obj_refl_lightshad_worldspace_prog_
|
|
: obj_refl_lightshad_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadowDoubleSided: {
|
|
// FIXME: This shader isn't actually flipping the normal for the
|
|
// back side of the face.. for now we don't care though.
|
|
SetDoubleSided_(true);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
int world_space = buffer->GetInt();
|
|
|
|
// Verified.
|
|
float r, g, b, reflect_r, reflect_g, reflect_b;
|
|
buffer->GetFloats(&r, &g, &b, &reflect_r, &reflect_g, &reflect_b);
|
|
ProgramObjectGL* p;
|
|
|
|
// Testing why reflection is wonky.
|
|
if (explicit_bool(false)) {
|
|
p = world_space ? obj_lightshad_worldspace_prog_
|
|
: obj_lightshad_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
buffer->GetTexture();
|
|
} else {
|
|
p = world_space ? obj_refl_lightshad_worldspace_prog_
|
|
: obj_refl_lightshad_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
}
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadowColorized: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
float r, g, b, reflect_r, reflect_g, reflect_b, colorize_r,
|
|
colorize_g, colorize_b;
|
|
buffer->GetFloats(&r, &g, &b, &reflect_r, &reflect_g, &reflect_b,
|
|
&colorize_r, &colorize_g, &colorize_b);
|
|
ProgramObjectGL* p = obj_refl_lightshad_colorize_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadowColorized2: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
|
|
float r, g, b, reflect_r, reflect_g, reflect_b, colorize_r,
|
|
colorize_g, colorize_b, colorize2_r, colorize2_g, colorize2_b;
|
|
buffer->GetFloats(&r, &g, &b, &reflect_r, &reflect_g, &reflect_b,
|
|
&colorize_r, &colorize_g, &colorize_b,
|
|
&colorize2_r, &colorize2_g, &colorize2_b);
|
|
ProgramObjectGL* p = obj_refl_lightshad_colorize2_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorize2Color(colorize2_r, colorize2_g, colorize2_b);
|
|
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadowAdd: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
float r, g, b, add_r, add_g, add_b, reflect_r, reflect_g, reflect_b;
|
|
buffer->GetFloats(&r, &g, &b, &add_r, &add_g, &add_b, &reflect_r,
|
|
&reflect_g, &reflect_b);
|
|
ProgramObjectGL* p = obj_refl_lightshad_add_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetAddColor(add_r, add_g, add_b);
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadowAddColorized: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
|
|
float r, g, b, add_r, add_g, add_b, reflect_r, reflect_g, reflect_b,
|
|
colorize_r, colorize_g, colorize_b;
|
|
buffer->GetFloats(&r, &g, &b, &add_r, &add_g, &add_b, &reflect_r,
|
|
&reflect_g, &reflect_b, &colorize_r, &colorize_g,
|
|
&colorize_b);
|
|
ProgramObjectGL* p = obj_refl_lightshad_add_colorize_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetAddColor(add_r, add_g, add_b);
|
|
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectLightShadowAddColorized2: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
auto light_shadow = static_cast<LightShadowType>(buffer->GetInt());
|
|
|
|
float r, g, b, add_r, add_g, add_b, reflect_r, reflect_g, reflect_b,
|
|
colorize_r, colorize_g, colorize_b, colorize2_r, colorize2_g,
|
|
colorize2_b;
|
|
buffer->GetFloats(&r, &g, &b, &add_r, &add_g, &add_b, &reflect_r,
|
|
&reflect_g, &reflect_b, &colorize_r, &colorize_g,
|
|
&colorize_b, &colorize2_r, &colorize2_g,
|
|
&colorize2_b);
|
|
ProgramObjectGL* p = obj_refl_lightshad_add_colorize2_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetAddColor(add_r, add_g, add_b);
|
|
|
|
p->SetColorizeTexture(buffer->GetTexture());
|
|
p->SetColorizeColor(colorize_r, colorize_g, colorize_b);
|
|
p->SetColorize2Color(colorize2_r, colorize2_g, colorize2_b);
|
|
|
|
p->SetReflectionTexture(buffer->GetTexture());
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
|
|
p->SetVignetteTexture(vignette_tex_);
|
|
GLuint light_shadow_tex;
|
|
switch (light_shadow) {
|
|
case LightShadowType::kTerrain:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
case LightShadowType::kObject:
|
|
light_shadow_tex =
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture();
|
|
break;
|
|
default:
|
|
light_shadow_tex = 0;
|
|
FatalError("Unhandled LightShadowType.");
|
|
}
|
|
p->SetLightShadowTexture(light_shadow_tex);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflect: {
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
int world_space = buffer->GetInt();
|
|
// verified
|
|
float r, g, b, reflect_r, reflect_g, reflect_b;
|
|
buffer->GetFloats(&r, &g, &b, &reflect_r, &reflect_g, &reflect_b);
|
|
ProgramObjectGL* p =
|
|
world_space ? obj_refl_worldspace_prog_ : obj_refl_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetReflectionTexture(buffer->GetTexture()); // reflection
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectTransparent: {
|
|
SetDoubleSided_(false);
|
|
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, reflect_r, reflect_g, reflect_b;
|
|
buffer->GetFloats(&r, &g, &b, &a, &reflect_r, &reflect_g,
|
|
&reflect_b);
|
|
ProgramObjectGL* p = obj_refl_transparent_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetReflectionTexture(buffer->GetTexture()); // reflection
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
break;
|
|
}
|
|
case ShadingType::kObjectReflectAddTransparent: {
|
|
SetDoubleSided_(false);
|
|
|
|
bool premult = static_cast<bool>(buffer->GetInt());
|
|
SetBlend(true);
|
|
SetBlendPremult(premult);
|
|
float r, g, b, a, add_r, add_g, add_b, reflect_r, reflect_g,
|
|
reflect_b;
|
|
buffer->GetFloats(&r, &g, &b, &a, &add_r, &add_g, &add_b,
|
|
&reflect_r, &reflect_g, &reflect_b);
|
|
ProgramObjectGL* p = obj_refl_add_transparent_prog_;
|
|
p->Bind();
|
|
p->SetColor(r, g, b, a);
|
|
p->SetColorTexture(buffer->GetTexture());
|
|
p->SetAddColor(add_r, add_g, add_b);
|
|
p->SetReflectionTexture(buffer->GetTexture()); // reflection
|
|
p->SetReflectionMult(reflect_r, reflect_g, reflect_b);
|
|
break;
|
|
}
|
|
case ShadingType::kShield: {
|
|
SetDoubleSided_(true);
|
|
SetBlend(true);
|
|
SetBlendPremult(true);
|
|
ProgramShieldGL* p = shield_prog_;
|
|
p->Bind();
|
|
p->SetDepthTexture(
|
|
static_cast<RenderTargetGL*>(camera_render_target())
|
|
->framebuffer()
|
|
->depth_texture());
|
|
break;
|
|
}
|
|
case ShadingType::kSpecial: {
|
|
SetDoubleSided_(false);
|
|
|
|
// If we ever need to use non-blend version of this in real
|
|
// renders, we should split off a non-blend version.
|
|
SetBlend(true);
|
|
SetBlendPremult(true);
|
|
auto source = (SpecialComponent::Source)buffer->GetInt();
|
|
ProgramSimpleGL* p = simple_tex_mod_prog_;
|
|
p->Bind();
|
|
switch (source) {
|
|
case SpecialComponent::Source::kLightBuffer:
|
|
p->SetColorTexture(
|
|
static_cast<RenderTargetGL*>(light_render_target())
|
|
->framebuffer()
|
|
->texture());
|
|
break;
|
|
case SpecialComponent::Source::kLightShadowBuffer:
|
|
p->SetColorTexture(
|
|
static_cast<RenderTargetGL*>(light_shadow_render_target())
|
|
->framebuffer()
|
|
->texture());
|
|
break;
|
|
case SpecialComponent::Source::kVROverlayBuffer: {
|
|
p->SetColorTexture(static_cast<RenderTargetGL*>(
|
|
vr_overlay_flat_render_target())
|
|
->framebuffer()
|
|
->texture());
|
|
p->SetColor(1, 1, 1, 0.95f);
|
|
break;
|
|
}
|
|
default:
|
|
FatalError("Unhandled SpecialComponent type.");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FatalError("Unhandled Shader Type.");
|
|
}
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kSimpleComponentInlineColor: {
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
auto* p = static_cast<ProgramSimpleGL*>(GetActiveProgram_());
|
|
assert(p != nullptr
|
|
&& p == dynamic_cast<ProgramSimpleGL*>(GetActiveProgram_()));
|
|
p->SetColor(r, g, b, a);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kObjectComponentInlineColor: {
|
|
float r, g, b, a;
|
|
buffer->GetFloats(&r, &g, &b, &a);
|
|
auto* p = static_cast<ProgramObjectGL*>(GetActiveProgram_());
|
|
assert(p != nullptr
|
|
&& p == dynamic_cast<ProgramObjectGL*>(GetActiveProgram_()));
|
|
p->SetColor(r, g, b, a);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kObjectComponentInlineAddColor: {
|
|
float r, g, b;
|
|
buffer->GetFloats(&r, &g, &b);
|
|
auto* p = static_cast<ProgramObjectGL*>(GetActiveProgram_());
|
|
assert(p != nullptr
|
|
&& p == dynamic_cast<ProgramObjectGL*>(GetActiveProgram_()));
|
|
p->SetAddColor(r, g, b);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kDrawMeshAsset: {
|
|
int flags = buffer->GetInt();
|
|
const MeshAsset* m = buffer->GetMesh();
|
|
assert(m);
|
|
auto mesh =
|
|
static_cast_check_type<MeshAssetDataGL*>(m->renderer_data());
|
|
assert(mesh);
|
|
|
|
// if they don't wanna draw in reflections...
|
|
if ((flags & kMeshDrawFlagNoReflection) && drawing_reflection()) {
|
|
break;
|
|
}
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
mesh->Bind();
|
|
mesh->Draw();
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kDrawMeshAssetInstanced: {
|
|
int flags = buffer->GetInt();
|
|
const MeshAsset* m = buffer->GetMesh();
|
|
assert(m);
|
|
auto mesh =
|
|
static_cast_check_type<MeshAssetDataGL*>(m->renderer_data());
|
|
assert(mesh);
|
|
Matrix44f* mats;
|
|
int count;
|
|
mats = buffer->GetMatrices(&count);
|
|
// if they don't wanna draw in reflections...
|
|
if ((flags & kMeshDrawFlagNoReflection) && drawing_reflection()) {
|
|
break;
|
|
}
|
|
mesh->Bind();
|
|
for (int i = 0; i < count; i++) {
|
|
g_base->graphics_server->PushTransform();
|
|
g_base->graphics_server->MultMatrix(mats[i]);
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
mesh->Draw();
|
|
g_base->graphics_server->PopTransform();
|
|
}
|
|
break;
|
|
}
|
|
// NOLINTNEXTLINE(bugprone-branch-clone)
|
|
case RenderCommandBuffer::Command::kBeginDebugDrawTriangles: {
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
#if BA_GL_ENABLE_DEBUG_DRAW_COMMANDS
|
|
glBegin(GL_TRIANGLES);
|
|
#endif
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kBeginDebugDrawLines: {
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
#if BA_GL_ENABLE_DEBUG_DRAW_COMMANDS
|
|
glBegin(GL_LINES);
|
|
#endif
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kEndDebugDraw: {
|
|
#if BA_GL_ENABLE_DEBUG_DRAW_COMMANDS
|
|
glEnd();
|
|
#endif
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kDebugDrawVertex3: {
|
|
float x, y, z;
|
|
buffer->GetFloats(&x, &y, &z);
|
|
#if BA_GL_ENABLE_DEBUG_DRAW_COMMANDS
|
|
glVertex3f(x, y, z);
|
|
#endif
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kDrawMesh: {
|
|
int flags = buffer->GetInt();
|
|
auto* mesh = buffer->GetMeshRendererData<MeshDataGL>();
|
|
assert(mesh);
|
|
if ((flags & kMeshDrawFlagNoReflection) && drawing_reflection()) {
|
|
break;
|
|
}
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
mesh->Bind();
|
|
mesh->Draw(DrawType::kTriangles);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kDrawScreenQuad: {
|
|
// Save proj/mv matrices, set up to draw a simple screen quad at the
|
|
// back of our depth range, draw, and restore.
|
|
Matrix44f old_model_view_matrix =
|
|
g_base->graphics_server->model_view_matrix();
|
|
Matrix44f old_projection_matrix =
|
|
g_base->graphics_server->projection_matrix();
|
|
g_base->graphics_server->SetModelViewMatrix(kMatrix44fIdentity);
|
|
g_base->graphics_server->SetOrthoProjection(-1, 1, -1, 1, -1, 0.01f);
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
g_base->graphics_server->SetModelViewMatrix(old_model_view_matrix);
|
|
g_base->graphics_server->SetProjectionMatrix(old_projection_matrix);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kScissorPush: {
|
|
Rect r;
|
|
buffer->GetFloats(&r.l, &r.b, &r.r, &r.t);
|
|
|
|
// Convert scissor-values from model space to view space.
|
|
// this of course assumes there's no rotations and whatnot.
|
|
Vector3f bot_left_pt = g_base->graphics_server->model_view_matrix()
|
|
* Vector3f(r.l, r.b, 0);
|
|
Vector3f top_right_pt = g_base->graphics_server->model_view_matrix()
|
|
* Vector3f(r.r, r.t, 0);
|
|
r.l = bot_left_pt.x;
|
|
r.b = bot_left_pt.y;
|
|
r.r = top_right_pt.x;
|
|
r.t = top_right_pt.y;
|
|
ScissorPush_(r, render_target);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kScissorPop: {
|
|
ScissorPop_(render_target);
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kPushTransform: {
|
|
g_base->graphics_server->PushTransform();
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kTranslate2: {
|
|
float x, y;
|
|
buffer->GetFloats(&x, &y);
|
|
g_base->graphics_server->Translate(Vector3f(x, y, 0));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kTranslate3: {
|
|
float x, y, z;
|
|
buffer->GetFloats(&x, &y, &z);
|
|
g_base->graphics_server->Translate(Vector3f(x, y, z));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kCursorTranslate: {
|
|
float x, y;
|
|
g_base->app_adapter->CursorPositionForDraw(&x, &y);
|
|
g_base->graphics_server->Translate(Vector3f(x, y, 0));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kScale2: {
|
|
float x, y;
|
|
buffer->GetFloats(&x, &y);
|
|
g_base->graphics_server->scale(Vector3f(x, y, 1.0f));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kScale3: {
|
|
float x, y, z;
|
|
buffer->GetFloats(&x, &y, &z);
|
|
g_base->graphics_server->scale(Vector3f(x, y, z));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kScaleUniform: {
|
|
float s = buffer->GetFloat();
|
|
g_base->graphics_server->scale(Vector3f(s, s, s));
|
|
break;
|
|
}
|
|
#if BA_VR_BUILD
|
|
case RenderCommandBuffer::Command::kTransformToRightHand: {
|
|
VRTransformToRightHand();
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kTransformToLeftHand: {
|
|
VRTransformToLeftHand();
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kTransformToHead: {
|
|
VRTransformToHead();
|
|
break;
|
|
}
|
|
#endif // BA_VR_BUILD
|
|
case RenderCommandBuffer::Command::kTranslateToProjectedPoint: {
|
|
float x, y, z;
|
|
buffer->GetFloats(&x, &y, &z);
|
|
Vector3f t = pass.frame_def()->beauty_pass()->tex_project_matrix()
|
|
* Vector3f(x, y, z);
|
|
g_base->graphics_server->Translate(Vector3f(
|
|
t.x * g_base->graphics_server->screen_virtual_width(),
|
|
t.y * g_base->graphics_server->screen_virtual_height(), 0));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kRotate: {
|
|
float angle, x, y, z;
|
|
buffer->GetFloats(&angle, &x, &y, &z);
|
|
g_base->graphics_server->Rotate(angle, Vector3f(x, y, z));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kMultMatrix: {
|
|
g_base->graphics_server->MultMatrix(*(buffer->GetMatrix()));
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kPopTransform: {
|
|
g_base->graphics_server->PopTransform();
|
|
break;
|
|
}
|
|
case RenderCommandBuffer::Command::kFlipCullFace: {
|
|
FlipCullFace();
|
|
break;
|
|
}
|
|
default:
|
|
throw Exception("Invalid command in render-command-buffer");
|
|
}
|
|
}
|
|
assert(buffer->IsEmpty());
|
|
} // NOLINT (yes this is too long)
|
|
|
|
void RendererGL::BlitBuffer(RenderTarget* src_in, RenderTarget* dst_in,
|
|
bool depth, bool linear_interpolation,
|
|
bool force_shader_mode, bool invalidate_source) {
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
auto* src = static_cast<RenderTargetGL*>(src_in);
|
|
assert(src && src == dynamic_cast<RenderTargetGL*>(src_in));
|
|
auto* dst = static_cast<RenderTargetGL*>(dst_in);
|
|
assert(dst && dst == dynamic_cast<RenderTargetGL*>(dst_in));
|
|
|
|
bool do_shader_blit{true};
|
|
|
|
// If they want depth we *MUST* use glBlitFramebuffer and can't have
|
|
// linear interp.
|
|
if (depth) {
|
|
assert(!force_shader_mode);
|
|
linear_interpolation = false;
|
|
}
|
|
// Use glBlitFramebuffer when its available.
|
|
// FIXME: This should be available in ES3.
|
|
// #if !BA_OSTYPE_IOS_TVOS
|
|
if (!force_shader_mode) {
|
|
do_shader_blit = false;
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, src->GetFramebufferID());
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->GetFramebufferID());
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
|
|
glBlitFramebuffer(0, 0, static_cast<GLint>(src->physical_width()),
|
|
static_cast<GLint>(src->physical_height()), 0, 0,
|
|
static_cast<GLint>(dst->physical_width()),
|
|
static_cast<GLint>(dst->physical_height()),
|
|
depth ? (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
: GL_COLOR_BUFFER_BIT,
|
|
linear_interpolation ? GL_LINEAR : GL_NEAREST);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
if (invalidate_source) {
|
|
InvalidateFramebuffer(true, depth, true);
|
|
}
|
|
} else {
|
|
do_shader_blit = true;
|
|
}
|
|
// #endif
|
|
if (do_shader_blit) {
|
|
SetDepthWriting(false);
|
|
SetDepthTesting(false);
|
|
dst_in->DrawBegin(false);
|
|
g_base->graphics_server->ModelViewReset();
|
|
g_base->graphics_server->SetOrthoProjection(-1, 1, -1, 1, -1, 1);
|
|
|
|
// Copied from ShadingType::kSimpleColor.
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
ProgramSimpleGL* p = simple_tex_prog_;
|
|
p->Bind();
|
|
p->SetColorTexture(src->framebuffer()->texture());
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
}
|
|
|
|
void RendererGL::ScissorPush_(const Rect& r_in, RenderTarget* render_target) {
|
|
if (scissor_rects_.empty()) {
|
|
glEnable(GL_SCISSOR_TEST);
|
|
scissor_rects_.push_back(r_in);
|
|
} else {
|
|
Rect r;
|
|
Rect rp = scissor_rects_.back();
|
|
r.l = r_in.l > rp.l ? r_in.l : rp.l;
|
|
r.r = r_in.r < rp.r ? r_in.r : rp.r;
|
|
r.b = r_in.b > rp.b ? r_in.b : rp.b;
|
|
r.t = r_in.t < rp.t ? r_in.t : rp.t;
|
|
scissor_rects_.push_back(r);
|
|
}
|
|
Rect clip = scissor_rects_.back();
|
|
if (clip.l > clip.r) {
|
|
clip.l = clip.r;
|
|
}
|
|
if (clip.b > clip.t) {
|
|
clip.b = clip.t;
|
|
}
|
|
auto* glt = static_cast<RenderTargetGL*>(render_target);
|
|
float scissor_scale_x =
|
|
static_cast<RenderTargetGL*>(render_target)->GetScissorScaleX();
|
|
float scissor_scale_y =
|
|
static_cast<RenderTargetGL*>(render_target)->GetScissorScaleY();
|
|
glScissor(static_cast<GLint>(glt->GetScissorX(clip.l)),
|
|
static_cast<GLint>(glt->GetScissorY(clip.b)),
|
|
static_cast<GLsizei>(scissor_scale_x * (clip.r - clip.l)),
|
|
static_cast<GLsizei>(scissor_scale_y * (clip.t - clip.b)));
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
|
|
void RendererGL::ScissorPop_(RenderTarget* render_target) {
|
|
BA_PRECONDITION(!scissor_rects_.empty());
|
|
scissor_rects_.pop_back();
|
|
if (scissor_rects_.empty()) {
|
|
glDisable(GL_SCISSOR_TEST);
|
|
} else {
|
|
Rect clip = scissor_rects_.back();
|
|
if (clip.l > clip.r) {
|
|
clip.l = clip.r;
|
|
}
|
|
if (clip.b > clip.t) {
|
|
clip.b = clip.t;
|
|
}
|
|
auto* glt = static_cast<RenderTargetGL*>(render_target);
|
|
float scissor_scale_x =
|
|
static_cast<RenderTargetGL*>(render_target)->GetScissorScaleX();
|
|
float scissor_scale_y =
|
|
static_cast<RenderTargetGL*>(render_target)->GetScissorScaleY();
|
|
glScissor(static_cast<GLint>(glt->GetScissorX(clip.l)),
|
|
static_cast<GLint>(glt->GetScissorY(clip.b)),
|
|
static_cast<GLsizei>(scissor_scale_x * (clip.r - clip.l)),
|
|
static_cast<GLsizei>(scissor_scale_y * (clip.t - clip.b)));
|
|
}
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
|
|
void RendererGL::SetDepthWriting(bool enable) {
|
|
if (enable != depth_writing_enabled_) {
|
|
depth_writing_enabled_ = enable;
|
|
glDepthMask(static_cast<GLboolean>(enable));
|
|
}
|
|
}
|
|
|
|
void RendererGL::SetDrawAtEqualDepth(bool enable) {
|
|
if (enable != draw_at_equal_depth_) {
|
|
draw_at_equal_depth_ = enable;
|
|
if (enable) {
|
|
glDepthFunc(GL_LEQUAL);
|
|
} else {
|
|
glDepthFunc(GL_LESS);
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME FIXME FIXME FIXME:
|
|
//
|
|
// Turning off GL_DEPTH_TEST also disables depth writing which we may not
|
|
// want. It sounds like the proper thing to do in that case is leave
|
|
// GL_DEPTH_TEST on and set glDepthFunc(GL_ALWAYS).
|
|
|
|
void RendererGL::SetDepthTesting(bool enable) {
|
|
if (enable != depth_testing_enabled_) {
|
|
depth_testing_enabled_ = enable;
|
|
if (enable) {
|
|
glEnable(GL_DEPTH_TEST);
|
|
} else {
|
|
glDisable(GL_DEPTH_TEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RendererGL::SetDepthRange(float min, float max) {
|
|
if (min != depth_range_min_ || max != depth_range_max_) {
|
|
depth_range_min_ = min;
|
|
depth_range_max_ = max;
|
|
glDepthRange(min, max);
|
|
}
|
|
}
|
|
|
|
void RendererGL::FlipCullFace() {
|
|
draw_front_ = !draw_front_;
|
|
if (draw_front_) {
|
|
glCullFace(GL_BACK);
|
|
} else {
|
|
glCullFace(GL_FRONT);
|
|
}
|
|
}
|
|
|
|
void RendererGL::SetBlend(bool b) {
|
|
#if !BA_GL_ENABLE_BLEND
|
|
b = false;
|
|
#endif
|
|
if (blend_ != b) {
|
|
blend_ = b;
|
|
if (blend_) {
|
|
glEnable(GL_BLEND);
|
|
} else {
|
|
glDisable(GL_BLEND);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RendererGL::SetBlendPremult(bool b) {
|
|
if (blend_premult_ != b) {
|
|
blend_premult_ = b;
|
|
if (blend_premult_) {
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
} else {
|
|
// currently we only ever write to an alpha buffer for our vr overlay
|
|
// texture, and in that case we need alpha to accumulate; not get
|
|
// overwritten. could probably enable this everywhere but I don't know if
|
|
// it's supported on all hardware or is slower or whatnot..
|
|
if (g_core->vr_mode()) {
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
|
|
GL_ONE_MINUS_SRC_ALPHA);
|
|
} else {
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RendererGL::BindVertexArray_(GLuint v) {
|
|
if (v != current_vertex_array_) {
|
|
glBindVertexArray(v);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
current_vertex_array_ = v;
|
|
}
|
|
}
|
|
|
|
void RendererGL::SetDoubleSided_(bool d) {
|
|
if (double_sided_ != d) {
|
|
double_sided_ = d;
|
|
if (double_sided_) {
|
|
glDisable(GL_CULL_FACE);
|
|
} else {
|
|
glEnable(GL_CULL_FACE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RendererGL::UpdateVignetteTex_(bool force) {
|
|
if (force || vignette_quality_ != g_base->graphics_server->quality()
|
|
|| vignette_tex_outer_r_ != vignette_outer().x
|
|
|| vignette_tex_outer_g_ != vignette_outer().y
|
|
|| vignette_tex_outer_b_ != vignette_outer().z
|
|
|| vignette_tex_inner_r_ != vignette_inner().x
|
|
|| vignette_tex_inner_g_ != vignette_inner().y
|
|
|| vignette_tex_inner_b_ != vignette_inner().z) {
|
|
vignette_tex_outer_r_ = vignette_outer().x;
|
|
vignette_tex_outer_g_ = vignette_outer().y;
|
|
vignette_tex_outer_b_ = vignette_outer().z;
|
|
vignette_tex_inner_r_ = vignette_inner().x;
|
|
vignette_tex_inner_g_ = vignette_inner().y;
|
|
vignette_tex_inner_b_ = vignette_inner().z;
|
|
vignette_quality_ = g_base->graphics_server->quality();
|
|
|
|
const int width = 64;
|
|
const int height = 64;
|
|
const size_t tex_buffer_size = width * height * 4;
|
|
std::vector<uint8_t> datavec(tex_buffer_size);
|
|
uint8_t* data{datavec.data()};
|
|
float max_c = 0.5f * 0.5f * 0.5f * 0.5f;
|
|
uint8_t* b = data;
|
|
|
|
float out_r = std::min(
|
|
255.0f, std::max(0.0f, 255.0f * (1.0f - vignette_tex_outer_r_)));
|
|
float out_g = std::min(
|
|
255.0f, std::max(0.0f, 255.0f * (1.0f - vignette_tex_outer_g_)));
|
|
float out_b = std::min(
|
|
255.0f, std::max(0.0f, 255.0f * (1.0f - vignette_tex_outer_b_)));
|
|
float in_r = std::min(
|
|
255.0f, std::max(0.0f, 255.0f * (1.0f - vignette_tex_inner_r_)));
|
|
float in_g = std::min(
|
|
255.0f, std::max(0.0f, 255.0f * (1.0f - vignette_tex_inner_g_)));
|
|
float in_b = std::min(
|
|
255.0f, std::max(0.0f, 255.0f * (1.0f - vignette_tex_inner_b_)));
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
float d3 = static_cast<float>(y) / (height - 1);
|
|
float d4 = 1.0f - d3;
|
|
for (int x = 0; x < width; x++) {
|
|
float d1 = static_cast<float>(x) / (width - 1);
|
|
float d2 = 1.0f - d1;
|
|
float c = 1.0f * (1.0f - ((d1 * d2 * d3 * d4) / max_c));
|
|
c = 0.5f * (c * c) + 0.5f * c;
|
|
c = std::min(1.0f, std::max(0.0f, c));
|
|
|
|
b[0] = static_cast<uint8_t>(c * out_r + (1.0f - c) * in_r);
|
|
b[1] = static_cast<uint8_t>(c * out_g + (1.0f - c) * in_g);
|
|
b[2] = static_cast<uint8_t>(c * out_b + (1.0f - c) * in_b);
|
|
b[3] = 255; // alpha
|
|
b += 4;
|
|
}
|
|
}
|
|
|
|
glGetError(); // Clear any error.
|
|
BindTexture_(GL_TEXTURE_2D, vignette_tex_);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, data);
|
|
|
|
// If 32 bit failed for some reason, attempt 16.
|
|
GLenum err = glGetError();
|
|
if (err != GL_NO_ERROR) {
|
|
static bool reported = false;
|
|
if (!reported) {
|
|
Log(LogName::kBaGraphics, LogLevel::kError,
|
|
"32-bit vignette creation failed; falling back to 16.");
|
|
reported = true;
|
|
}
|
|
const int kVignetteTexWidth = 64;
|
|
const int kVignetteTexHeight = 32;
|
|
const int kVignetteTexBufferSize = kVignetteTexWidth * kVignetteTexHeight;
|
|
uint16_t data2[kVignetteTexBufferSize];
|
|
float max_c2 = 0.5f * 0.5f * 0.5f * 0.5f;
|
|
uint16_t* b2 = data2;
|
|
|
|
float out_r2 = std::min(
|
|
32.0f, std::max(0.0f, 32.0f * (1.0f - vignette_tex_outer_r_)));
|
|
float out_g2 = std::min(
|
|
64.0f, std::max(0.0f, 64.0f * (1.0f - vignette_tex_outer_g_)));
|
|
float out_b2 = std::min(
|
|
32.0f, std::max(0.0f, 32.0f * (1.0f - vignette_tex_outer_b_)));
|
|
float in_r2 = std::min(
|
|
32.0f, std::max(0.0f, 32.0f * (1.0f - vignette_tex_inner_r_)));
|
|
float in_g2 = std::min(
|
|
64.0f, std::max(0.0f, 64.0f * (1.0f - vignette_tex_inner_g_)));
|
|
float in_b2 = std::min(
|
|
32.0f, std::max(0.0f, 32.0f * (1.0f - vignette_tex_inner_b_)));
|
|
|
|
// IMPORTANT - if we tweak anything here we need to tweak vertex
|
|
// shaders that calc this on the fly as well..
|
|
for (int y = 0; y < height; y++) {
|
|
float d3 = static_cast<float>(y) / (height - 1);
|
|
float d4 = 1.0f - d3;
|
|
for (int x = 0; x < width; x++) {
|
|
float d1 = static_cast<float>(x) / (width - 1);
|
|
float d2 = 1.0f - d1;
|
|
float c = 1.0f * (1.0f - ((d1 * d2 * d3 * d4) / max_c2));
|
|
c = 0.5f * (c * c) + 0.5f * c;
|
|
c = std::min(1.0f, std::max(0.0f, c));
|
|
int red =
|
|
std::min(31, static_cast<int>(c * out_r2 + (1.0f - c) * in_r2));
|
|
int green =
|
|
std::min(63, static_cast<int>(c * out_g2 + (1.0f - c) * in_g2));
|
|
int blue =
|
|
std::min(31, static_cast<int>(c * out_b2 + (1.0f - c) * in_b2));
|
|
*b2 = static_cast<uint16_t>(red << 11 | green << 5 | blue);
|
|
b2 += 1;
|
|
}
|
|
}
|
|
BindTexture_(GL_TEXTURE_2D, vignette_tex_);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, data2);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
if (force) {
|
|
BA_GL_LABEL_OBJECT(GL_TEXTURE, vignette_tex_, "vignetteTex");
|
|
}
|
|
}
|
|
}
|
|
|
|
auto RendererGL::GetFunkyDepthIssue_() -> bool {
|
|
if (!funky_depth_issue_set_) {
|
|
BA_LOG_ONCE(LogName::kBaGraphics, LogLevel::kError,
|
|
"fetching funky depth issue but not set");
|
|
}
|
|
return funky_depth_issue_;
|
|
}
|
|
|
|
#if BA_OSTYPE_ANDROID
|
|
std::string RendererGL::GetAutoAndroidRes() {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
|
|
// Simplifying this to just 1080p for anything we label 'speedy' and 720p
|
|
// for everything else.
|
|
if (is_speedy_android_device_) {
|
|
return "1080p";
|
|
}
|
|
return "720p";
|
|
}
|
|
#endif // BA_OSTYPE_ANDROID
|
|
|
|
auto RendererGL::GetAutoTextureQuality() -> TextureQuality {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
|
|
TextureQuality qual{TextureQuality::kHigh};
|
|
|
|
#if BA_OSTYPE_ANDROID
|
|
{
|
|
// Lets be cheaper in VR mode since we have to draw twice.
|
|
if (g_core->vr_mode()) {
|
|
qual = TextureQuality::kHigh;
|
|
} else {
|
|
qual = TextureQuality::kHigh;
|
|
}
|
|
}
|
|
#else // BA_OSTYPE_ANDROID
|
|
|
|
// On other platforms (iOS, mac, pc, etc) just default to high.
|
|
qual = TextureQuality::kHigh;
|
|
|
|
#endif // BA_OSTYPE_ANDROID
|
|
|
|
return qual;
|
|
}
|
|
|
|
auto RendererGL::GetAutoGraphicsQuality() -> GraphicsQuality {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
GraphicsQuality q{GraphicsQuality::kMedium};
|
|
#if BA_OSTYPE_ANDROID
|
|
// lets be cheaper in VR mode since we draw twice..
|
|
if (g_core->vr_mode()) {
|
|
q = GraphicsQuality::kMedium;
|
|
} else {
|
|
if (is_speedy_android_device_) {
|
|
q = GraphicsQuality::kHigher;
|
|
} else {
|
|
q = GraphicsQuality::kHigh;
|
|
}
|
|
}
|
|
#else
|
|
// Elsewhere just assume we're working with something speedy.
|
|
q = GraphicsQuality::kHigher;
|
|
#endif
|
|
return q;
|
|
}
|
|
|
|
void RendererGL::RetainShader_(ProgramGL* p) { shaders_.emplace_back(p); }
|
|
|
|
void RendererGL::Load() {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
assert(!data_loaded_);
|
|
assert(g_base->graphics_server->graphics_quality()
|
|
!= GraphicsQuality::kUnset);
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
if (!got_screen_framebuffer_) {
|
|
got_screen_framebuffer_ = true;
|
|
|
|
// Grab the current framebuffer and consider that to be our 'screen'
|
|
// framebuffer. This can be 0 for the main framebuffer or can be
|
|
// something else.
|
|
screen_framebuffer_ = GLGetInt(GL_FRAMEBUFFER_BINDING);
|
|
}
|
|
Renderer::Load();
|
|
int high_qual_pp_flag =
|
|
g_base->graphics_server->quality() >= GraphicsQuality::kHigher
|
|
? SHD_HIGHER_QUALITY
|
|
: 0;
|
|
screen_mesh_ = std::make_unique<MeshDataSimpleFullGL>(this);
|
|
VertexSimpleFull v[] = {{{-1, -1, 0}, {0, 0}},
|
|
{{1, -1, 0}, {65535, 0}},
|
|
{{1, 1, 0}, {65535, 65535}},
|
|
{{-1, 1, 0}, {0, 65535}}};
|
|
const uint16_t indices[] = {0, 1, 2, 0, 2, 3};
|
|
MeshBuffer<VertexSimpleFull> buffer(4, v);
|
|
buffer.state = 1; // Necessary for this to set properly.
|
|
MeshIndexBuffer16 i_buffer(6, indices);
|
|
i_buffer.state = 1; // Necessary for this to set properly.
|
|
screen_mesh_->SetData(&buffer);
|
|
screen_mesh_->SetIndexData(&i_buffer);
|
|
assert(shaders_.empty());
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
ProgramGL* p;
|
|
p = simple_color_prog_ = new ProgramSimpleGL(this, SHD_MODULATE);
|
|
RetainShader_(p);
|
|
p = simple_tex_prog_ = new ProgramSimpleGL(this, SHD_TEXTURE);
|
|
RetainShader_(p);
|
|
p = simple_tex_dtest_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_DEPTH_BUG_TEST);
|
|
RetainShader_(p);
|
|
|
|
// Have to run this after we've created the shader to be able to test it.
|
|
CheckFunkyDepthIssue_();
|
|
p = simple_tex_mod_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_MODULATE);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_flatness_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_MODULATE | SHD_FLATNESS);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_shadow_prog_ = new ProgramSimpleGL(
|
|
this, SHD_TEXTURE | SHD_MODULATE | SHD_SHADOW | SHD_MASK_UV2);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_shadow_flatness_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_MODULATE | SHD_SHADOW
|
|
| SHD_MASK_UV2 | SHD_FLATNESS);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_glow_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_MODULATE | SHD_GLOW);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_glow_maskuv2_prog_ = new ProgramSimpleGL(
|
|
this, SHD_TEXTURE | SHD_MODULATE | SHD_GLOW | SHD_MASK_UV2);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_colorized_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_MODULATE | SHD_COLORIZE);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_colorized2_prog_ = new ProgramSimpleGL(
|
|
this, SHD_TEXTURE | SHD_MODULATE | SHD_COLORIZE | SHD_COLORIZE2);
|
|
RetainShader_(p);
|
|
p = simple_tex_mod_colorized2_masked_prog_ =
|
|
new ProgramSimpleGL(this, SHD_TEXTURE | SHD_MODULATE | SHD_COLORIZE
|
|
| SHD_COLORIZE2 | SHD_MASKED);
|
|
RetainShader_(p);
|
|
p = obj_prog_ = new ProgramObjectGL(this, 0);
|
|
RetainShader_(p);
|
|
p = obj_transparent_prog_ = new ProgramObjectGL(this, SHD_OBJ_TRANSPARENT);
|
|
RetainShader_(p);
|
|
p = obj_lightshad_transparent_prog_ =
|
|
new ProgramObjectGL(this, SHD_OBJ_TRANSPARENT | SHD_LIGHT_SHADOW);
|
|
RetainShader_(p);
|
|
p = obj_refl_prog_ = new ProgramObjectGL(this, SHD_REFLECTION);
|
|
RetainShader_(p);
|
|
p = obj_refl_worldspace_prog_ =
|
|
new ProgramObjectGL(this, SHD_REFLECTION | SHD_WORLD_SPACE_PTS);
|
|
RetainShader_(p);
|
|
p = obj_refl_transparent_prog_ =
|
|
new ProgramObjectGL(this, SHD_REFLECTION | SHD_OBJ_TRANSPARENT);
|
|
RetainShader_(p);
|
|
p = obj_refl_add_transparent_prog_ =
|
|
new ProgramObjectGL(this, SHD_REFLECTION | SHD_ADD | SHD_OBJ_TRANSPARENT);
|
|
RetainShader_(p);
|
|
p = obj_lightshad_prog_ = new ProgramObjectGL(this, SHD_LIGHT_SHADOW);
|
|
RetainShader_(p);
|
|
p = obj_lightshad_worldspace_prog_ =
|
|
new ProgramObjectGL(this, SHD_LIGHT_SHADOW | SHD_WORLD_SPACE_PTS);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_prog_ =
|
|
new ProgramObjectGL(this, SHD_LIGHT_SHADOW | SHD_REFLECTION);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_worldspace_prog_ = new ProgramObjectGL(
|
|
this, SHD_LIGHT_SHADOW | SHD_REFLECTION | SHD_WORLD_SPACE_PTS);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_colorize_prog_ = new ProgramObjectGL(
|
|
this, SHD_LIGHT_SHADOW | SHD_REFLECTION | SHD_COLORIZE);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_colorize2_prog_ = new ProgramObjectGL(
|
|
this, SHD_LIGHT_SHADOW | SHD_REFLECTION | SHD_COLORIZE | SHD_COLORIZE2);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_add_prog_ =
|
|
new ProgramObjectGL(this, SHD_LIGHT_SHADOW | SHD_REFLECTION | SHD_ADD);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_add_colorize_prog_ = new ProgramObjectGL(
|
|
this, SHD_LIGHT_SHADOW | SHD_REFLECTION | SHD_ADD | SHD_COLORIZE);
|
|
RetainShader_(p);
|
|
p = obj_refl_lightshad_add_colorize2_prog_ =
|
|
new ProgramObjectGL(this, SHD_LIGHT_SHADOW | SHD_REFLECTION | SHD_ADD
|
|
| SHD_COLORIZE | SHD_COLORIZE2);
|
|
RetainShader_(p);
|
|
p = smoke_prog_ =
|
|
new ProgramSmokeGL(this, SHD_OBJ_TRANSPARENT | SHD_WORLD_SPACE_PTS);
|
|
RetainShader_(p);
|
|
p = smoke_overlay_prog_ = new ProgramSmokeGL(
|
|
this, SHD_OBJ_TRANSPARENT | SHD_WORLD_SPACE_PTS | SHD_OVERLAY);
|
|
RetainShader_(p);
|
|
p = sprite_prog_ = new ProgramSpriteGL(this, SHD_COLOR);
|
|
RetainShader_(p);
|
|
p = sprite_camalign_prog_ =
|
|
new ProgramSpriteGL(this, SHD_CAMERA_ALIGNED | SHD_COLOR);
|
|
RetainShader_(p);
|
|
p = sprite_camalign_overlay_prog_ =
|
|
new ProgramSpriteGL(this, SHD_CAMERA_ALIGNED | SHD_OVERLAY | SHD_COLOR);
|
|
RetainShader_(p);
|
|
p = blur_prog_ = new ProgramBlurGL(this, 0);
|
|
RetainShader_(p);
|
|
p = shield_prog_ = new ProgramShieldGL(this, 0);
|
|
RetainShader_(p);
|
|
|
|
// Conditional seems to be a *very* slight win on some architectures (A7),
|
|
// a loss on some (A5) and a wash on some (Adreno 320). Gonna wait before
|
|
// a clean win before turning it on.
|
|
p = postprocess_prog_ = new ProgramPostProcessGL(this, high_qual_pp_flag);
|
|
RetainShader_(p);
|
|
if (g_base->graphics_server->quality() >= GraphicsQuality::kHigher) {
|
|
p = postprocess_eyes_prog_ = new ProgramPostProcessGL(this, SHD_EYES);
|
|
RetainShader_(p);
|
|
} else {
|
|
postprocess_eyes_prog_ = nullptr;
|
|
}
|
|
p = postprocess_distort_prog_ =
|
|
new ProgramPostProcessGL(this, SHD_DISTORT | high_qual_pp_flag);
|
|
RetainShader_(p);
|
|
|
|
// Generate our random value texture.
|
|
// TODO(ericf): move this to assets.
|
|
{
|
|
glGenTextures(1, &random_tex_);
|
|
BindTexture_(GL_TEXTURE_2D, random_tex_);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
const int tex_buffer_size = 128 * 128 * 3;
|
|
unsigned char data[tex_buffer_size];
|
|
for (unsigned char& i : data) {
|
|
i = static_cast<unsigned char>(rand()); // NOLINT
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 128, 128, 0, GL_RGB,
|
|
GL_UNSIGNED_BYTE, data);
|
|
BA_GL_LABEL_OBJECT(GL_TEXTURE, random_tex_, "randomTex");
|
|
}
|
|
|
|
// Generate our vignette tex.
|
|
// TODO(ericf): move this to assets.
|
|
{
|
|
glGenTextures(1, &vignette_tex_);
|
|
BindTexture_(GL_TEXTURE_2D, vignette_tex_);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
UpdateVignetteTex_(true);
|
|
}
|
|
|
|
// Let's pre-fill our recyclable mesh-datas list to reduce the need to
|
|
// make more which could cause hitches.
|
|
assert(recycle_mesh_datas_simple_split_.empty());
|
|
for (int i = 0; i < 10; i++) {
|
|
recycle_mesh_datas_simple_split_.push_back(new MeshDataSimpleSplitGL(this));
|
|
}
|
|
assert(recycle_mesh_datas_object_split_.empty());
|
|
for (int i = 0; i < 10; i++) {
|
|
recycle_mesh_datas_object_split_.push_back(new MeshDataObjectSplitGL(this));
|
|
}
|
|
assert(recycle_mesh_datas_simple_full_.empty());
|
|
for (int i = 0; i < 10; i++) {
|
|
recycle_mesh_datas_simple_full_.push_back(new MeshDataSimpleFullGL(this));
|
|
}
|
|
assert(recycle_mesh_datas_dual_texture_full_.empty());
|
|
for (int i = 0; i < 10; i++) {
|
|
recycle_mesh_datas_dual_texture_full_.push_back(
|
|
new MeshDataDualTextureFullGL(this));
|
|
}
|
|
assert(recycle_mesh_datas_smoke_full_.empty());
|
|
for (int i = 0; i < 2; i++) {
|
|
recycle_mesh_datas_smoke_full_.push_back(new MeshDataSmokeFullGL(this));
|
|
}
|
|
assert(recycle_mesh_datas_sprite_.empty());
|
|
for (int i = 0; i < 2; i++) {
|
|
recycle_mesh_datas_sprite_.push_back(new MeshDataSpriteGL(this));
|
|
}
|
|
|
|
// Re-sync with the GL state since we might be dealing with a new
|
|
// context/etc.
|
|
SyncGLState_();
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
data_loaded_ = true;
|
|
}
|
|
|
|
void RendererGL::PostLoad() {
|
|
Renderer::PostLoad();
|
|
// Control may pass back to cardboard after we've finished loading but
|
|
// before we render, (in cases such as graphics settings switches) ...and
|
|
// it seems they can screw up our VAOs if we leave them bound. So lets be
|
|
// defensive.
|
|
#if BA_CARDBOARD_BUILD
|
|
SyncGLState_();
|
|
#endif
|
|
}
|
|
|
|
void RendererGL::Unload() {
|
|
assert(g_base->app_adapter->InGraphicsContext());
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
assert(data_loaded_);
|
|
Renderer::Unload();
|
|
// clear out recycle-mesh-datas
|
|
for (auto&& i : recycle_mesh_datas_simple_split_) {
|
|
delete i;
|
|
}
|
|
recycle_mesh_datas_simple_split_.clear();
|
|
for (auto&& i : recycle_mesh_datas_object_split_) {
|
|
delete i;
|
|
}
|
|
recycle_mesh_datas_object_split_.clear();
|
|
for (auto&& i : recycle_mesh_datas_simple_full_) {
|
|
delete i;
|
|
}
|
|
recycle_mesh_datas_simple_full_.clear();
|
|
for (auto&& i : recycle_mesh_datas_dual_texture_full_) {
|
|
delete i;
|
|
}
|
|
recycle_mesh_datas_dual_texture_full_.clear();
|
|
for (auto&& i : recycle_mesh_datas_smoke_full_) {
|
|
delete i;
|
|
}
|
|
recycle_mesh_datas_smoke_full_.clear();
|
|
for (auto&& i : recycle_mesh_datas_sprite_) {
|
|
delete i;
|
|
}
|
|
recycle_mesh_datas_sprite_.clear();
|
|
screen_mesh_.reset();
|
|
if (!g_base->graphics_server->renderer_context_lost()) {
|
|
glDeleteTextures(1, &random_tex_);
|
|
glDeleteTextures(1, &vignette_tex_);
|
|
}
|
|
blur_buffers_.clear();
|
|
shaders_.clear();
|
|
simple_color_prog_ = nullptr;
|
|
simple_tex_prog_ = nullptr;
|
|
simple_tex_dtest_prog_ = nullptr;
|
|
simple_tex_mod_prog_ = nullptr;
|
|
simple_tex_mod_flatness_prog_ = nullptr;
|
|
simple_tex_mod_shadow_prog_ = nullptr;
|
|
simple_tex_mod_shadow_flatness_prog_ = nullptr;
|
|
simple_tex_mod_glow_prog_ = nullptr;
|
|
simple_tex_mod_glow_maskuv2_prog_ = nullptr;
|
|
simple_tex_mod_colorized_prog_ = nullptr;
|
|
simple_tex_mod_colorized2_prog_ = nullptr;
|
|
simple_tex_mod_colorized2_masked_prog_ = nullptr;
|
|
obj_prog_ = nullptr;
|
|
obj_transparent_prog_ = nullptr;
|
|
obj_refl_prog_ = nullptr;
|
|
obj_refl_worldspace_prog_ = nullptr;
|
|
obj_refl_transparent_prog_ = nullptr;
|
|
obj_refl_add_transparent_prog_ = nullptr;
|
|
obj_lightshad_prog_ = nullptr;
|
|
obj_lightshad_worldspace_prog_ = nullptr;
|
|
obj_refl_lightshad_prog_ = nullptr;
|
|
obj_refl_lightshad_worldspace_prog_ = nullptr;
|
|
obj_refl_lightshad_colorize_prog_ = nullptr;
|
|
obj_refl_lightshad_colorize2_prog_ = nullptr;
|
|
obj_refl_lightshad_add_prog_ = nullptr;
|
|
obj_refl_lightshad_add_colorize_prog_ = nullptr;
|
|
obj_refl_lightshad_add_colorize2_prog_ = nullptr;
|
|
smoke_prog_ = nullptr;
|
|
smoke_overlay_prog_ = nullptr;
|
|
sprite_prog_ = nullptr;
|
|
sprite_camalign_prog_ = nullptr;
|
|
sprite_camalign_overlay_prog_ = nullptr;
|
|
obj_lightshad_transparent_prog_ = nullptr;
|
|
blur_prog_ = nullptr;
|
|
shield_prog_ = nullptr;
|
|
postprocess_prog_ = nullptr;
|
|
postprocess_eyes_prog_ = nullptr;
|
|
postprocess_distort_prog_ = nullptr;
|
|
data_loaded_ = false;
|
|
BA_DEBUG_CHECK_GL_ERROR;
|
|
}
|
|
|
|
auto RendererGL::NewMeshAssetData(const MeshAsset& model)
|
|
-> Object::Ref<MeshAssetRendererData> {
|
|
return Object::New<MeshAssetRendererData, MeshAssetDataGL>(model, this);
|
|
}
|
|
|
|
auto RendererGL::NewTextureData(const TextureAsset& texture)
|
|
-> Object::Ref<TextureAssetRendererData> {
|
|
return Object::New<TextureAssetRendererData, TextureDataGL>(texture, this);
|
|
}
|
|
|
|
auto RendererGL::NewScreenRenderTarget() -> RenderTarget* {
|
|
return Object::NewDeferred<RenderTargetGL>(this);
|
|
}
|
|
|
|
auto RendererGL::NewFramebufferRenderTarget(int width, int height,
|
|
bool linear_interp, bool depth,
|
|
bool texture, bool depth_texture,
|
|
bool high_quality, bool msaa,
|
|
bool alpha)
|
|
-> Object::Ref<RenderTarget> {
|
|
return Object::New<RenderTarget, RenderTargetGL>(
|
|
this, width, height, linear_interp, depth, texture, depth_texture,
|
|
high_quality, msaa, alpha);
|
|
}
|
|
|
|
auto RendererGL::NewMeshData(MeshDataType mesh_type, MeshDrawType draw_type)
|
|
-> MeshRendererData* {
|
|
switch (mesh_type) {
|
|
case MeshDataType::kIndexedSimpleSplit: {
|
|
MeshDataSimpleSplitGL* data;
|
|
// Use a recycled one if we've got one; otherwise create a new one.
|
|
auto i = recycle_mesh_datas_simple_split_.rbegin();
|
|
if (i != recycle_mesh_datas_simple_split_.rend()) {
|
|
data = *i;
|
|
recycle_mesh_datas_simple_split_.pop_back();
|
|
} else {
|
|
data = new MeshDataSimpleSplitGL(this);
|
|
}
|
|
return data;
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedObjectSplit: {
|
|
MeshDataObjectSplitGL* data;
|
|
// Use a recycled one if we've got one; otherwise create a new one.
|
|
auto i = recycle_mesh_datas_object_split_.rbegin();
|
|
if (i != recycle_mesh_datas_object_split_.rend()) {
|
|
data = *i;
|
|
recycle_mesh_datas_object_split_.pop_back();
|
|
} else {
|
|
data = new MeshDataObjectSplitGL(this);
|
|
}
|
|
return data;
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedSimpleFull: {
|
|
MeshDataSimpleFullGL* data;
|
|
// Use a recycled one if we've got one; otherwise create a new one.
|
|
auto i = recycle_mesh_datas_simple_full_.rbegin();
|
|
if (i != recycle_mesh_datas_simple_full_.rend()) {
|
|
data = *i;
|
|
recycle_mesh_datas_simple_full_.pop_back();
|
|
} else {
|
|
data = new MeshDataSimpleFullGL(this);
|
|
}
|
|
data->set_dynamic_draw(draw_type == MeshDrawType::kDynamic);
|
|
return data;
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedDualTextureFull: {
|
|
MeshDataDualTextureFullGL* data;
|
|
// Use a recycled one if we've got one; otherwise create a new one.
|
|
auto i = recycle_mesh_datas_dual_texture_full_.rbegin();
|
|
if (i != recycle_mesh_datas_dual_texture_full_.rend()) {
|
|
data = *i;
|
|
recycle_mesh_datas_dual_texture_full_.pop_back();
|
|
} else {
|
|
data = new MeshDataDualTextureFullGL(this);
|
|
}
|
|
data->set_dynamic_draw(draw_type == MeshDrawType::kDynamic);
|
|
return data;
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedSmokeFull: {
|
|
MeshDataSmokeFullGL* data;
|
|
// Use a recycled one if we've got one; otherwise create a new one.
|
|
auto i = recycle_mesh_datas_smoke_full_.rbegin();
|
|
if (i != recycle_mesh_datas_smoke_full_.rend()) {
|
|
data = *i;
|
|
recycle_mesh_datas_smoke_full_.pop_back();
|
|
} else {
|
|
data = new MeshDataSmokeFullGL(this);
|
|
}
|
|
data->set_dynamic_draw(draw_type == MeshDrawType::kDynamic);
|
|
return data;
|
|
break;
|
|
}
|
|
case MeshDataType::kSprite: {
|
|
MeshDataSpriteGL* data;
|
|
// Use a recycled one if we've got one; otherwise create a new one.
|
|
auto i = recycle_mesh_datas_sprite_.rbegin();
|
|
if (i != recycle_mesh_datas_sprite_.rend()) {
|
|
data = *i;
|
|
recycle_mesh_datas_sprite_.pop_back();
|
|
} else {
|
|
data = new MeshDataSpriteGL(this);
|
|
}
|
|
data->set_dynamic_draw(draw_type == MeshDrawType::kDynamic);
|
|
return data;
|
|
break;
|
|
}
|
|
default:
|
|
throw Exception();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RendererGL::DeleteMeshData(MeshRendererData* source_in,
|
|
MeshDataType mesh_type) {
|
|
// When we're done with mesh-data we keep it around for recycling. It
|
|
// seems that killing off VAO/VBOs can be hitchy (on mac at least). Hmmm
|
|
// should we have some sort of threshold at which point we kill off some?
|
|
|
|
switch (mesh_type) {
|
|
case MeshDataType::kIndexedSimpleSplit: {
|
|
auto source = static_cast<MeshDataSimpleSplitGL*>(source_in);
|
|
assert(source
|
|
&& source == dynamic_cast<MeshDataSimpleSplitGL*>(source_in));
|
|
source->Reset();
|
|
recycle_mesh_datas_simple_split_.push_back(source);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedObjectSplit: {
|
|
auto source = static_cast<MeshDataObjectSplitGL*>(source_in);
|
|
assert(source
|
|
&& source == dynamic_cast<MeshDataObjectSplitGL*>(source_in));
|
|
source->Reset();
|
|
recycle_mesh_datas_object_split_.push_back(source);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedSimpleFull: {
|
|
auto source = static_cast<MeshDataSimpleFullGL*>(source_in);
|
|
assert(source
|
|
&& source == dynamic_cast<MeshDataSimpleFullGL*>(source_in));
|
|
source->Reset();
|
|
recycle_mesh_datas_simple_full_.push_back(source);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedDualTextureFull: {
|
|
auto source = static_cast<MeshDataDualTextureFullGL*>(source_in);
|
|
assert(source
|
|
&& source == dynamic_cast<MeshDataDualTextureFullGL*>(source_in));
|
|
source->Reset();
|
|
recycle_mesh_datas_dual_texture_full_.push_back(source);
|
|
break;
|
|
}
|
|
case MeshDataType::kIndexedSmokeFull: {
|
|
auto source = static_cast<MeshDataSmokeFullGL*>(source_in);
|
|
assert(source && source == dynamic_cast<MeshDataSmokeFullGL*>(source_in));
|
|
source->Reset();
|
|
recycle_mesh_datas_smoke_full_.push_back(source);
|
|
break;
|
|
}
|
|
case MeshDataType::kSprite: {
|
|
auto source = static_cast<MeshDataSpriteGL*>(source_in);
|
|
assert(source && source == dynamic_cast<MeshDataSpriteGL*>(source_in));
|
|
source->Reset();
|
|
recycle_mesh_datas_sprite_.push_back(source);
|
|
break;
|
|
}
|
|
default:
|
|
throw Exception();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RendererGL::CheckForErrors() {
|
|
// Lets only check periodically. I doubt it hurts to run this all the time
|
|
// but just in case.
|
|
error_check_counter_++;
|
|
if (error_check_counter_ > 120) {
|
|
error_check_counter_ = 0;
|
|
BA_CHECK_GL_ERROR;
|
|
}
|
|
}
|
|
|
|
void RendererGL::DrawDebug() {
|
|
if (explicit_bool(false)) {
|
|
// Draw our cam buffer if we have it.
|
|
if (has_camera_render_target()) {
|
|
SetDepthWriting(false);
|
|
SetDepthTesting(false);
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
ProgramSimpleGL* p = simple_tex_prog_;
|
|
p->Bind();
|
|
|
|
g_base->graphics_server->ModelViewReset();
|
|
g_base->graphics_server->SetOrthoProjection(-1, 1, -1, 1, -1, 1);
|
|
|
|
float tx = -0.6f;
|
|
float ty = 0.6f;
|
|
|
|
g_base->graphics_server->PushTransform();
|
|
g_base->graphics_server->scale(Vector3f(0.4f, 0.4f, 0.4f));
|
|
g_base->graphics_server->Translate(Vector3f(-1.3f, -0.7f, 0));
|
|
|
|
// Draw cam buffer.
|
|
g_base->graphics_server->PushTransform();
|
|
g_base->graphics_server->Translate(Vector3f(tx, ty, 0));
|
|
tx += 0.2f;
|
|
ty -= 0.25f;
|
|
g_base->graphics_server->scale(Vector3f(0.5f, 0.5f, 1.0f));
|
|
p->SetColorTexture(static_cast<RenderTargetGL*>(camera_render_target())
|
|
->framebuffer()
|
|
->texture());
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
g_base->graphics_server->PopTransform();
|
|
|
|
// Draw blur buffers.
|
|
if (explicit_bool(false)) {
|
|
for (auto&& i : blur_buffers_) {
|
|
g_base->graphics_server->PushTransform();
|
|
g_base->graphics_server->Translate(Vector3f(tx, ty, 0));
|
|
tx += 0.2f;
|
|
ty -= 0.25f;
|
|
g_base->graphics_server->scale(Vector3f(0.5f, 0.5f, 1.0f));
|
|
p->SetColorTexture(i->texture());
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
g_base->graphics_server->PopTransform();
|
|
}
|
|
}
|
|
g_base->graphics_server->PopTransform();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RendererGL::GenerateCameraBufferBlurPasses() {
|
|
// If our cam-buffer res has changed since last time, regenerate our blur
|
|
// buffers.
|
|
auto* cam_buffer = static_cast<RenderTargetGL*>(camera_render_target());
|
|
assert(cam_buffer != nullptr
|
|
&& dynamic_cast<RenderTargetGL*>(camera_render_target())
|
|
== cam_buffer);
|
|
|
|
if (cam_buffer->physical_width() != last_cam_buffer_width_
|
|
|| cam_buffer->physical_height() != last_cam_buffer_height_
|
|
|| blur_res_count() != last_blur_res_count_ || blur_buffers_.empty()) {
|
|
blur_buffers_.clear();
|
|
last_cam_buffer_width_ = cam_buffer->physical_width();
|
|
last_cam_buffer_height_ = cam_buffer->physical_height();
|
|
last_blur_res_count_ = blur_res_count();
|
|
int w = static_cast<int>(last_cam_buffer_width_);
|
|
int h = static_cast<int>(last_cam_buffer_height_);
|
|
|
|
// In higher-quality we do multiple levels and 16-bit dithering is kinda
|
|
// noticeable and ugly then.
|
|
bool high_quality_fbos =
|
|
(g_base->graphics_server->quality() >= GraphicsQuality::kHigher);
|
|
for (int i = 0; i < blur_res_count(); i++) {
|
|
assert(w % 2 == 0);
|
|
assert(h % 2 == 0);
|
|
w /= 2;
|
|
h /= 2;
|
|
blur_buffers_.push_back(Object::New<FramebufferObjectGL>(
|
|
this, w, h,
|
|
true, // linear_interp
|
|
false, // depth
|
|
true, // tex
|
|
false, // depth_tex
|
|
high_quality_fbos, // high_quality
|
|
false, // msaa
|
|
false // alpha
|
|
)); // NOLINT(whitespace/parens)
|
|
}
|
|
|
|
// Final redundant one (we run an extra blur without down-rezing).
|
|
if (g_base->graphics_server->quality() >= GraphicsQuality::kHigher)
|
|
blur_buffers_.push_back(Object::New<FramebufferObjectGL>(
|
|
this, w, h,
|
|
true, // linear_interp
|
|
false, // depth
|
|
true, // tex
|
|
false, // depth_tex
|
|
false, // high_quality
|
|
false, // msaa
|
|
false // alpha
|
|
)); // NOLINT(whitespace/parens)
|
|
}
|
|
|
|
// Ok now go through and do the blurring.
|
|
SetDepthWriting(false);
|
|
SetDepthTesting(false);
|
|
g_base->graphics_server->ModelViewReset();
|
|
g_base->graphics_server->SetOrthoProjection(-1, 1, -1, 1, -1, 1);
|
|
SetDoubleSided_(false);
|
|
SetBlend(false);
|
|
|
|
ProgramBlurGL* p = blur_prog_;
|
|
p->Bind();
|
|
|
|
FramebufferObjectGL* src_fb =
|
|
static_cast<RenderTargetGL*>(camera_render_target())->framebuffer();
|
|
for (auto&& i : blur_buffers_) {
|
|
FramebufferObjectGL* fb = i.Get();
|
|
assert(fb);
|
|
fb->Bind();
|
|
SetViewport_(0, 0, fb->width(), fb->height());
|
|
InvalidateFramebuffer(true, false, false);
|
|
p->SetColorTexture(src_fb->texture());
|
|
if (fb->width() == src_fb->width()) { // Our last one is equal res.
|
|
p->SetPixelSize(2.0f / static_cast<float>(fb->width()),
|
|
2.0f / static_cast<float>(fb->height()));
|
|
} else {
|
|
p->SetPixelSize(1.0f / static_cast<float>(fb->width()),
|
|
1.0f / static_cast<float>(fb->height()));
|
|
}
|
|
GetActiveProgram_()->PrepareToDraw();
|
|
screen_mesh_->Bind();
|
|
screen_mesh_->Draw(DrawType::kTriangles);
|
|
src_fb = fb;
|
|
}
|
|
}
|
|
|
|
void RendererGL::CardboardDisableScissor() { glDisable(GL_SCISSOR_TEST); }
|
|
|
|
void RendererGL::CardboardEnableScissor() { glEnable(GL_SCISSOR_TEST); }
|
|
|
|
void RendererGL::VREyeRenderBegin() {
|
|
assert(g_core->vr_mode());
|
|
|
|
// On rift we need to turn off srgb conversion for each eye render so we
|
|
// can dump our linear data into oculus' srgb buffer as-is. (we really
|
|
// should add proper srgb support to the engine at some point).
|
|
#if BA_RIFT_BUILD
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
#endif // BA_RIFT_BUILD
|
|
|
|
screen_framebuffer_ = GLGetInt(GL_FRAMEBUFFER_BINDING);
|
|
}
|
|
|
|
#if BA_VR_BUILD
|
|
void RendererGL::VRSyncRenderStates() {
|
|
// GL state has been mucked with outside of our code; let's resync stuff.
|
|
SyncGLState_();
|
|
}
|
|
#endif // BA_VR_BUILD
|
|
|
|
void RendererGL::RenderFrameDefEnd() {
|
|
// Need to set some states to keep cardboard happy.
|
|
#if BA_CARDBOARD_BUILD
|
|
if (g_core->vr_mode()) {
|
|
SyncGLState_();
|
|
glEnable(GL_SCISSOR_TEST);
|
|
}
|
|
#endif // BA_CARDBOARD_BUILD
|
|
}
|
|
|
|
} // namespace ballistica::base
|
|
|
|
#endif // BA_ENABLE_OPENGL
|