mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-11-04 00:08:00 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,781 @@
|
||||
#if defined(RBX_PLATFORM_IOS) || defined(__ANDROID__)
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "GfxCore/Framebuffer.h"
|
||||
|
||||
#include "G3D/Quat.h"
|
||||
#include "G3D/Matrix4.h"
|
||||
|
||||
#include "rbx/threadsafe.h"
|
||||
#include "rbx/CEvent.h"
|
||||
|
||||
#include "rbx/Profiler.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
#import <CoreMotion/CMMotionManager.h>
|
||||
#import <UIKit/UIApplication.h>
|
||||
#import <UIKit/UIScreen.h>
|
||||
#import <QuartzCore/CABase.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/sensor.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
LOGGROUP(VR)
|
||||
|
||||
FASTFLAGVARIABLE(CardboardVR, false)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
// Configuration (data is kept globally accessible for ease of use from the app)
|
||||
struct CardboardConfiguration
|
||||
{
|
||||
float fieldOfView;
|
||||
float interLensDistance;
|
||||
float trayToLensCenterDistance; // aka verticalDistanceToLensCenter
|
||||
float screenToLensDistance;
|
||||
|
||||
float distortionCoeffs[2];
|
||||
|
||||
float screenWidth;
|
||||
float screenHeight;
|
||||
float screenBorderSize;
|
||||
};
|
||||
|
||||
// Google Cardboard - Technical Specification version 2.0 - September 2015
|
||||
static CardboardConfiguration gConfiguration = { 120, 0.0639f, 0.035f, 0.039f, { 0.34f, 0.55f }, 0, 0, 0.003f };
|
||||
static float gScreenOrientation = 1.f;
|
||||
|
||||
// Implementation
|
||||
const bool kUseVsync = true;
|
||||
const bool kUseTimeWarp = true;
|
||||
const bool kUseAdaptivePrediction = true;
|
||||
const bool kUseNeckModel = true;
|
||||
|
||||
using G3D::Quat;
|
||||
using G3D::Vector3;
|
||||
using G3D::Matrix4;
|
||||
|
||||
const Vector3 kNeckOffset(0, 0.075f, -0.0805f);
|
||||
|
||||
struct DistortionVertex
|
||||
{
|
||||
float x, y;
|
||||
float u, v;
|
||||
float fade;
|
||||
};
|
||||
|
||||
static float clamp(float t, float a, float b)
|
||||
{
|
||||
return std::min(std::max(t, a), b);
|
||||
}
|
||||
|
||||
static float distort(const CardboardConfiguration& configuration, float radius)
|
||||
{
|
||||
float result = 1.0f;
|
||||
float factor = 1.0f;
|
||||
|
||||
for (size_t i = 0; i < ARRAYSIZE(configuration.distortionCoeffs); ++i)
|
||||
{
|
||||
factor *= radius * radius;
|
||||
result += configuration.distortionCoeffs[i] * factor;
|
||||
}
|
||||
|
||||
return radius * result;
|
||||
}
|
||||
|
||||
static float distortInverse(const CardboardConfiguration& configuration, float radius)
|
||||
{
|
||||
float r0 = radius / 0.9f;
|
||||
float r = radius * 0.9f;
|
||||
float dr0 = radius - distort(configuration, r0);
|
||||
while (fabsf(r - r0) > 0.0001f)
|
||||
{
|
||||
float dr = radius - distort(configuration, r);
|
||||
float r2 = r - dr * ((r - r0) / (dr - dr0));
|
||||
r0 = r;
|
||||
r = r2;
|
||||
dr0 = dr;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void computeFOVPort(const CardboardConfiguration& configuration, float fovPort[4])
|
||||
{
|
||||
float fovTan = tanf(configuration.fieldOfView / 2 * (3.1415926f / 180));
|
||||
|
||||
float outerDistance = (configuration.screenWidth - configuration.interLensDistance) / 2.0f;
|
||||
float innerDistance = configuration.interLensDistance / 2.0f;
|
||||
float bottomDistance = configuration.trayToLensCenterDistance - configuration.screenBorderSize;
|
||||
float topDistance = configuration.screenHeight + configuration.screenBorderSize - configuration.trayToLensCenterDistance;
|
||||
|
||||
fovPort[0] = std::min(fovTan, distort(configuration, topDistance / configuration.screenToLensDistance));
|
||||
fovPort[1] = std::min(fovTan, distort(configuration, bottomDistance / configuration.screenToLensDistance));
|
||||
fovPort[2] = std::min(fovTan, distort(configuration, outerDistance / configuration.screenToLensDistance));
|
||||
fovPort[3] = std::min(fovTan, distort(configuration, innerDistance / configuration.screenToLensDistance));
|
||||
}
|
||||
|
||||
static shared_ptr<Geometry> createDistortionMesh(Device* device, const CardboardConfiguration& configuration, int eye, unsigned int* outIndices, float outUVScaleOffset[4])
|
||||
{
|
||||
float eyeSign = (eye == 0) ? -1 : 1;
|
||||
|
||||
float fovPort[4];
|
||||
computeFOVPort(configuration, fovPort);
|
||||
|
||||
float screenWidth = configuration.screenWidth / configuration.screenToLensDistance;
|
||||
float screenHeight = configuration.screenHeight / configuration.screenToLensDistance;
|
||||
float xEyeOffsetScreen = (configuration.screenWidth / 2.0f + eyeSign * configuration.interLensDistance / 2.0f) / configuration.screenToLensDistance;
|
||||
float yEyeOffsetScreen = (configuration.trayToLensCenterDistance - configuration.screenBorderSize) / configuration.screenToLensDistance;
|
||||
|
||||
float textureWidth = fovPort[2] + fovPort[3];
|
||||
float textureHeight = fovPort[0] + fovPort[1];
|
||||
float xEyeOffsetTexture = (eye == 0) ? fovPort[2] : fovPort[3];
|
||||
float yEyeOffsetTexture = fovPort[1];
|
||||
|
||||
float vignetteSizeTanAngle = 0.05f;
|
||||
|
||||
const int rows = 40;
|
||||
const int cols = 40;
|
||||
const unsigned int vertexCount = rows * cols;
|
||||
const unsigned int indexCount = (rows - 1) * cols * 2 + (rows - 2);
|
||||
|
||||
std::vector<VertexLayout::Element> elements;
|
||||
elements.push_back(VertexLayout::Element(0, offsetof(DistortionVertex, x), VertexLayout::Format_Float2, VertexLayout::Semantic_Position));
|
||||
elements.push_back(VertexLayout::Element(0, offsetof(DistortionVertex, u), VertexLayout::Format_Float3, VertexLayout::Semantic_Texture));
|
||||
|
||||
shared_ptr<VertexLayout> layout = device->createVertexLayout(elements);
|
||||
|
||||
shared_ptr<VertexBuffer> vb = device->createVertexBuffer(sizeof(DistortionVertex), vertexCount, GeometryBuffer::Usage_Static);
|
||||
|
||||
DistortionVertex* vbptr = static_cast<DistortionVertex*>(vb->lock());
|
||||
|
||||
for (int row = 0; row < rows; ++row)
|
||||
{
|
||||
for (int col = 0; col < cols; ++col)
|
||||
{
|
||||
float uTexture = col / float(cols - 1);
|
||||
float vTexture = row / float(rows - 1);
|
||||
|
||||
float xTexture = uTexture * textureWidth - xEyeOffsetTexture;
|
||||
float yTexture = vTexture * textureHeight - yEyeOffsetTexture;
|
||||
float rTexture = sqrtf(xTexture * xTexture + yTexture * yTexture);
|
||||
|
||||
float textureToScreen = (rTexture > 0.0f) ? distortInverse(configuration, rTexture) / rTexture : 1.0f;
|
||||
|
||||
float xScreen = xTexture * textureToScreen;
|
||||
float yScreen = yTexture * textureToScreen;
|
||||
|
||||
float uScreen = (xScreen + xEyeOffsetScreen) / screenWidth;
|
||||
float vScreen = (yScreen + yEyeOffsetScreen) / screenHeight;
|
||||
|
||||
float vignetteSizeTexture = vignetteSizeTanAngle / textureToScreen;
|
||||
|
||||
float dxTexture = xTexture + xEyeOffsetTexture - clamp(xTexture + xEyeOffsetTexture, vignetteSizeTexture, textureWidth - vignetteSizeTexture);
|
||||
float dyTexture = yTexture + yEyeOffsetTexture - clamp(yTexture + yEyeOffsetTexture, vignetteSizeTexture, textureHeight - vignetteSizeTexture);
|
||||
float drTexture = sqrtf(dxTexture * dxTexture + dyTexture * dyTexture);
|
||||
|
||||
float vignette = 1.0f - clamp(drTexture / vignetteSizeTexture, 0.0f, 1.0f);
|
||||
|
||||
vbptr->x = 2.0f * uScreen - 1.0f;
|
||||
vbptr->y = 2.0f * vScreen - 1.0f;
|
||||
vbptr->u = xTexture;
|
||||
vbptr->v = yTexture;
|
||||
vbptr->fade = vignette;
|
||||
vbptr++;
|
||||
}
|
||||
}
|
||||
|
||||
vb->unlock();
|
||||
|
||||
shared_ptr<IndexBuffer> ib = device->createIndexBuffer(sizeof(unsigned short), indexCount, GeometryBuffer::Usage_Static);
|
||||
|
||||
unsigned short* ibptr = static_cast<unsigned short*>(ib->lock());
|
||||
|
||||
unsigned int vertexOffset = 0;
|
||||
|
||||
for (int row = 0; row < rows-1; ++row)
|
||||
{
|
||||
if (row > 0)
|
||||
{
|
||||
int last = ibptr[-1];
|
||||
|
||||
*ibptr++ = last;
|
||||
}
|
||||
|
||||
for (int col = 0; col < cols; ++col)
|
||||
{
|
||||
if (col > 0)
|
||||
{
|
||||
if (row % 2 == 0)
|
||||
vertexOffset++;
|
||||
else
|
||||
vertexOffset--;
|
||||
}
|
||||
|
||||
*ibptr++ = vertexOffset;
|
||||
*ibptr++ = vertexOffset + 40;
|
||||
}
|
||||
|
||||
vertexOffset += 40;
|
||||
}
|
||||
|
||||
ib->unlock();
|
||||
|
||||
*outIndices = indexCount;
|
||||
|
||||
outUVScaleOffset[0] = 1 / textureWidth;
|
||||
outUVScaleOffset[1] = 1 / textureHeight;
|
||||
outUVScaleOffset[2] = xEyeOffsetTexture / textureWidth;
|
||||
outUVScaleOffset[3] = yEyeOffsetTexture / textureHeight;
|
||||
|
||||
return device->createGeometry(layout, vb, ib);
|
||||
}
|
||||
|
||||
static shared_ptr<ShaderProgram> createDistortionProgram(Device* device)
|
||||
{
|
||||
std::string vertexSourceCommon = "\
|
||||
uniform vec4 UVScaleOffset;\
|
||||
uniform vec4 Warp;\
|
||||
vec3 qtransform(vec4 q, vec3 v) { return v + 2.0*cross(cross(v, q.xyz) + q.w*v, q.xyz); } \
|
||||
void main() {\
|
||||
gl_Position = vertex;\
|
||||
highp vec3 uvw = qtransform(Warp, vec3(uv0.xy, -1.0));\
|
||||
texcoord = vec3((uvw.xy / -uvw.z) * UVScaleOffset.xy + UVScaleOffset.zw, uv0.z);\
|
||||
}";
|
||||
|
||||
std::string vertexSourceGL2 = "attribute highp vec4 vertex;\nattribute highp vec3 uv0;\nvarying highp vec3 texcoord;\n" + vertexSourceCommon;
|
||||
std::string vertexSourceGL3 = "#version 300 es\nin highp vec4 vertex;\nin highp vec3 uv0;\nout highp vec3 texcoord;\n" + vertexSourceCommon;
|
||||
std::string fragmentSourceGL2 = "uniform sampler2D buffer;\nvarying highp vec3 texcoord;\nvoid main() { gl_FragData[0] = texture2D(buffer, texcoord.xy) * texcoord.z; }";
|
||||
std::string fragmentSourceGL3 = "#version 300 es\nuniform sampler2D buffer;\nout lowp vec4 _glFragData[1];\nin highp vec3 texcoord;\nvoid main() { _glFragData[0] = texture(buffer, texcoord.xy) * texcoord.z; }";
|
||||
|
||||
std::string vertexSource = (device->getShadingLanguage() == "glsles") ? vertexSourceGL2 : vertexSourceGL3;
|
||||
std::string fragmentSource = (device->getShadingLanguage() == "glsles") ? fragmentSourceGL2 : fragmentSourceGL3;
|
||||
|
||||
return device->createShaderProgram(
|
||||
device->createVertexShader(device->createShaderBytecode(vertexSource, "", "main")),
|
||||
device->createFragmentShader(device->createShaderBytecode(fragmentSource, "", "main")));
|
||||
}
|
||||
|
||||
static Quat predictRotation(double displayTimestamp, double sensorTimestamp, const Vector3& rotationRate)
|
||||
{
|
||||
float predictionDt = std::min(std::max(displayTimestamp - sensorTimestamp, 0.0), 0.1);
|
||||
|
||||
if (kUseAdaptivePrediction)
|
||||
{
|
||||
float angularSpeed = rotationRate.length();
|
||||
float candidateDt = angularSpeed * 0.2; // The rate at which the dynamic prediction interval varies
|
||||
|
||||
predictionDt = (angularSpeed > 0.001f) ? std::min(predictionDt, candidateDt) : 0;
|
||||
}
|
||||
|
||||
return Quat::fromRotation(predictionDt * rotationRate);
|
||||
}
|
||||
|
||||
static DeviceVR::Pose getPose(const Vector3& position, const Quat& orientation)
|
||||
{
|
||||
DeviceVR::Pose result = {};
|
||||
|
||||
result.valid = true;
|
||||
|
||||
result.position[0] = position.x;
|
||||
result.position[1] = position.y;
|
||||
result.position[2] = position.z;
|
||||
|
||||
result.orientation[0] = orientation.x;
|
||||
result.orientation[1] = orientation.y;
|
||||
result.orientation[2] = orientation.z;
|
||||
result.orientation[3] = orientation.w;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
class HeadTracker
|
||||
{
|
||||
public:
|
||||
HeadTracker()
|
||||
: gyroTimestamp(0)
|
||||
, orientationTimestamp(0)
|
||||
{
|
||||
motionManager = [[CMMotionManager alloc] init];
|
||||
queue = [[NSOperationQueue alloc] init];
|
||||
|
||||
motionManager.gyroUpdateInterval = 1 / 100.0;
|
||||
[motionManager startGyroUpdatesToQueue:queue withHandler:^(CMGyroData* gyroData, NSError*) {
|
||||
RBXPROFILER_SCOPE("VR", "updateSensors");
|
||||
RBXPROFILER_LABELF("VR", "gyro %.1f ms", (gyroData.timestamp - CACurrentMediaTime()) * 1000);
|
||||
|
||||
rbx::spin_mutex::scoped_lock lock(mutex);
|
||||
|
||||
gyro = Vector3(gyroData.rotationRate.x, gyroData.rotationRate.y, gyroData.rotationRate.z);
|
||||
gyroTimestamp = gyroData.timestamp;
|
||||
}];
|
||||
|
||||
motionManager.deviceMotionUpdateInterval = 1 / 100.0;
|
||||
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical toQueue:queue withHandler:^(CMDeviceMotion* motion, NSError*) {
|
||||
RBXPROFILER_SCOPE("VR", "updateSensors");
|
||||
RBXPROFILER_LABELF("VR", "orientation %.1f ms", (motion.timestamp - CACurrentMediaTime()) * 1000);
|
||||
|
||||
rbx::spin_mutex::scoped_lock lock(mutex);
|
||||
|
||||
orientation = Quat(motion.attitude.quaternion.x, motion.attitude.quaternion.y, motion.attitude.quaternion.z, motion.attitude.quaternion.w);
|
||||
orientationTimestamp = motion.timestamp;
|
||||
}];
|
||||
}
|
||||
|
||||
~HeadTracker()
|
||||
{
|
||||
[motionManager stopGyroUpdates];
|
||||
[motionManager stopDeviceMotionUpdates];
|
||||
|
||||
[motionManager release];
|
||||
[queue release];
|
||||
}
|
||||
|
||||
double getTime()
|
||||
{
|
||||
return CACurrentMediaTime();
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
return orientationTimestamp > 0;
|
||||
}
|
||||
|
||||
Quat predictOrientation(double time)
|
||||
{
|
||||
rbx::spin_mutex::scoped_lock lock(mutex);
|
||||
|
||||
if (!isValid()) return Quat();
|
||||
|
||||
return orientation * predictRotation(time, orientationTimestamp, gyro);
|
||||
}
|
||||
|
||||
private:
|
||||
CMMotionManager* motionManager;
|
||||
NSOperationQueue* queue;
|
||||
|
||||
rbx::spin_mutex mutex;
|
||||
|
||||
Vector3 gyro;
|
||||
double gyroTimestamp;
|
||||
|
||||
Quat orientation;
|
||||
double orientationTimestamp;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
enum
|
||||
{
|
||||
ASENSOR_TYPE_GAME_ROTATION = 15,
|
||||
};
|
||||
|
||||
class HeadTracker
|
||||
{
|
||||
public:
|
||||
HeadTracker()
|
||||
: looper(NULL)
|
||||
, looperReady(false)
|
||||
, gyroTimestamp(0)
|
||||
, gameRotationTimestamp(0)
|
||||
{
|
||||
boost::thread(boost::bind(processThread, this)).swap(thread);
|
||||
}
|
||||
|
||||
~HeadTracker()
|
||||
{
|
||||
looperReady.Wait();
|
||||
ALooper_wake(looper);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
double getTime()
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_BOOTTIME, &tv);
|
||||
|
||||
return tv.tv_sec + tv.tv_nsec / 1e9;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
return gameRotationTimestamp > 0;
|
||||
}
|
||||
|
||||
Quat predictOrientation(double time)
|
||||
{
|
||||
rbx::spin_mutex::scoped_lock lock(mutex);
|
||||
|
||||
if (!isValid()) return Quat();
|
||||
|
||||
Quat orientation(gameRotation, sqrtf(std::max(0.f, 1 - gameRotation.squaredLength())));
|
||||
|
||||
return orientation * predictRotation(time, gameRotationTimestamp, gyro);
|
||||
}
|
||||
|
||||
private:
|
||||
static void processThread(HeadTracker* self)
|
||||
{
|
||||
Profiler::onThreadCreate("SensorUpdate");
|
||||
|
||||
self->looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
|
||||
self->looperReady.Set();
|
||||
|
||||
ASensorManager* manager = ASensorManager_getInstance();
|
||||
ASensorEventQueue* eventQueue = ASensorManager_createEventQueue(manager, self->looper, 0, NULL, NULL);
|
||||
|
||||
FASTLOG(FLog::VR, "VR: Sensor thread started");
|
||||
|
||||
setupSensor(eventQueue, ASensorManager_getDefaultSensor(manager, ASENSOR_TYPE_GYROSCOPE));
|
||||
setupSensor(eventQueue, ASensorManager_getDefaultSensor(manager, ASENSOR_TYPE_GAME_ROTATION));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int rc = ALooper_pollOnce(-1, NULL, NULL, NULL);
|
||||
if (rc != 0)
|
||||
{
|
||||
FASTLOG1(FLog::VR, "VR: Stopping sensor thread (pollOnce returned %d)", rc);
|
||||
break;
|
||||
}
|
||||
|
||||
RBXPROFILER_SCOPE("VR", "updateSensors");
|
||||
|
||||
for (;;)
|
||||
{
|
||||
ASensorEvent eventBuffer[16];
|
||||
|
||||
ssize_t numEvents = ASensorEventQueue_getEvents(eventQueue, eventBuffer, ARRAYSIZE(eventBuffer));
|
||||
if (numEvents <= 0) break;
|
||||
|
||||
rbx::spin_mutex::scoped_lock lock(self->mutex);
|
||||
|
||||
double time = self->getTime();
|
||||
|
||||
for (ssize_t i = 0; i < numEvents; ++i)
|
||||
{
|
||||
const ASensorEvent& e = eventBuffer[i];
|
||||
|
||||
if (e.type == ASENSOR_TYPE_GYROSCOPE)
|
||||
{
|
||||
self->gyro = Vector3(e.data);
|
||||
self->gyroTimestamp = e.timestamp / 1e9;
|
||||
|
||||
RBXPROFILER_LABELF("VR", "gyro %.1f ms", (time - e.timestamp / 1e9) * 1000);
|
||||
}
|
||||
else if (e.type == ASENSOR_TYPE_GAME_ROTATION)
|
||||
{
|
||||
self->gameRotation = Vector3(e.data);
|
||||
self->gameRotationTimestamp = e.timestamp / 1e9;
|
||||
|
||||
RBXPROFILER_LABELF("VR", "orientation %.1f ms", (time - e.timestamp / 1e9) * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASensorManager_destroyEventQueue(manager, eventQueue);
|
||||
|
||||
Profiler::onThreadExit();
|
||||
}
|
||||
|
||||
static void setupSensor(ASensorEventQueue* eventQueue, const ASensor* sensor)
|
||||
{
|
||||
if (sensor)
|
||||
{
|
||||
int rate = ASensor_getMinDelay(sensor);
|
||||
|
||||
ASensorEventQueue_enableSensor(eventQueue, sensor);
|
||||
ASensorEventQueue_setEventRate(eventQueue, sensor, rate);
|
||||
|
||||
FASTLOG2(FLog::VR, "VR: Setup sensor %d with rate %d us", ASensor_getType(sensor), ASensor_getMinDelay(sensor));
|
||||
FASTLOGS(FLog::VR, "VR: Sensor %s", ASensor_getName(sensor));
|
||||
}
|
||||
}
|
||||
|
||||
ALooper* looper;
|
||||
CEvent looperReady;
|
||||
|
||||
boost::thread thread;
|
||||
rbx::spin_mutex mutex;
|
||||
|
||||
Vector3 gyro;
|
||||
double gyroTimestamp;
|
||||
|
||||
Vector3 gameRotation;
|
||||
double gameRotationTimestamp;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct CardboardVRGL: DeviceVRGL
|
||||
{
|
||||
Framebuffer* mainFramebuffer;
|
||||
|
||||
shared_ptr<Framebuffer> fb[2];
|
||||
shared_ptr<Texture> textures[2];
|
||||
|
||||
shared_ptr<ShaderProgram> distortionProgram;
|
||||
|
||||
shared_ptr<Geometry> distortionMesh[2];
|
||||
unsigned int distortionMeshIndices;
|
||||
float distortionUVScaleOffset[2][4];
|
||||
|
||||
HeadTracker headTracker;
|
||||
CardboardConfiguration configuration;
|
||||
|
||||
double displayTime;
|
||||
Quat headOrientation;
|
||||
Vector3 headPosition;
|
||||
|
||||
float headingRef;
|
||||
bool headingSet;
|
||||
|
||||
CardboardVRGL()
|
||||
{
|
||||
mainFramebuffer = NULL;
|
||||
|
||||
distortionMeshIndices = 0;
|
||||
memset(distortionUVScaleOffset, 0, sizeof(distortionUVScaleOffset));
|
||||
|
||||
displayTime = 0;
|
||||
|
||||
headingRef = 0;
|
||||
headingSet = 0;
|
||||
}
|
||||
|
||||
~CardboardVRGL()
|
||||
{
|
||||
}
|
||||
|
||||
Quat getHeadOrientation(const Quat& orientation)
|
||||
{
|
||||
float pi = G3D::pif();
|
||||
|
||||
float uiOrientationFlip = gScreenOrientation;
|
||||
Quat displayOrientation = Quat::fromAxisAngleRotation(Vector3::unitZ(), uiOrientationFlip * pi / 2);
|
||||
|
||||
return
|
||||
Quat::fromAxisAngleRotation(Vector3::unitX(), -pi / 2) *
|
||||
orientation * displayOrientation;
|
||||
}
|
||||
|
||||
Quat getHeadOrientationCentered(const Quat& orientation)
|
||||
{
|
||||
return
|
||||
Quat::fromAxisAngleRotation(Vector3::unitY(), -headingRef) *
|
||||
getHeadOrientation(orientation);
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
// 40 ms is the estimated time to display the content
|
||||
displayTime = headTracker.getTime() + 0.040;
|
||||
|
||||
Quat orientation = headTracker.predictOrientation(displayTime);
|
||||
|
||||
// reset heading when we get valid orientations
|
||||
if (!headingSet && headTracker.isValid())
|
||||
{
|
||||
Vector3 heading = getHeadOrientation(orientation) * Vector3(0, 0, -1);
|
||||
|
||||
if (fabsf(heading.y) < 0.95f)
|
||||
{
|
||||
float angle = atan2(-heading.x, -heading.z);
|
||||
|
||||
headingRef = angle;
|
||||
headingSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
// update head orientation
|
||||
headOrientation = getHeadOrientationCentered(orientation);
|
||||
|
||||
if (kUseNeckModel)
|
||||
{
|
||||
// update head position using neck model
|
||||
headPosition = headOrientation * kNeckOffset - Vector3(0, kNeckOffset.y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void recenter() override
|
||||
{
|
||||
headingSet = false;
|
||||
}
|
||||
|
||||
Framebuffer* getEyeFramebuffer(int eye) override
|
||||
{
|
||||
RBXASSERT(eye == 0 || eye == 1);
|
||||
|
||||
return fb[eye].get();
|
||||
}
|
||||
|
||||
State getState() override
|
||||
{
|
||||
State result = {};
|
||||
|
||||
result.headPose = getPose(headPosition, headOrientation);
|
||||
|
||||
float fovPort[4];
|
||||
computeFOVPort(configuration, fovPort);
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
float eyeSign = (eye == 0 ? -1 : 1);
|
||||
|
||||
result.eyeOffset[eye][0] = eyeSign * configuration.interLensDistance / 2;
|
||||
result.eyeOffset[eye][1] = 0;
|
||||
result.eyeOffset[eye][2] = 0;
|
||||
|
||||
result.eyeFov[eye][0] = fovPort[0];
|
||||
result.eyeFov[eye][1] = fovPort[1];
|
||||
result.eyeFov[eye][2] = (eye == 0) ? fovPort[2] : fovPort[3];
|
||||
result.eyeFov[eye][3] = (eye == 0) ? fovPort[3] : fovPort[2];
|
||||
}
|
||||
|
||||
result.needsMirror = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void submitFrame(DeviceContext* context) override
|
||||
{
|
||||
unsigned int screenWidth = mainFramebuffer->getWidth();
|
||||
unsigned int screenHeight = mainFramebuffer->getHeight();
|
||||
|
||||
context->bindFramebuffer(mainFramebuffer);
|
||||
|
||||
const float clearColor[] = {0, 0, 0, 0};
|
||||
context->clearFramebuffer(DeviceContext::Buffer_Color, clearColor, 0, 0);
|
||||
|
||||
context->bindProgram(distortionProgram.get());
|
||||
context->setBlendState(BlendState::Mode_None);
|
||||
context->setRasterizerState(RasterizerState::Cull_None);
|
||||
context->setDepthState(DepthState(DepthState::Function_Always, false));
|
||||
|
||||
Quat warp;
|
||||
|
||||
if (kUseTimeWarp)
|
||||
{
|
||||
Quat warpOrientation = headTracker.predictOrientation(displayTime);
|
||||
Quat warpHeadOrientation = getHeadOrientationCentered(warpOrientation);
|
||||
|
||||
warp = warpHeadOrientation.inverse() * headOrientation;
|
||||
}
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
context->setConstant(distortionProgram->getConstantHandle("UVScaleOffset"), distortionUVScaleOffset[eye], 1);
|
||||
context->setConstant(distortionProgram->getConstantHandle("Warp"), &warp.x, 1);
|
||||
|
||||
context->bindTexture(0, textures[eye].get(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp));
|
||||
context->draw(distortionMesh[eye].get(), Geometry::Primitive_TriangleStrip, 0, distortionMeshIndices, 0, 0);
|
||||
}
|
||||
|
||||
const int dividerWidth = 4;
|
||||
const float dividerColor[] = {1, 1, 1, 1};
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(screenWidth / 2 - dividerWidth / 2, 0, dividerWidth, screenHeight);
|
||||
|
||||
context->clearFramebuffer(DeviceContext::Buffer_Color, dividerColor, 0, 0);
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
void setup(Device* device) override
|
||||
{
|
||||
mainFramebuffer = device->getMainFramebuffer();
|
||||
|
||||
unsigned int width = 1024, height = 1024;
|
||||
|
||||
shared_ptr<Renderbuffer> depthStencil = device->createRenderbuffer(Texture::Format_D24S8, width, height, 1);
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
textures[eye] = device->createTexture(Texture::Type_2D, Texture::Format_RGBA8, width, height, 1, 1, Texture::Usage_Renderbuffer);
|
||||
fb[eye] = device->createFramebuffer(textures[eye]->getRenderbuffer(0, 0), depthStencil);
|
||||
}
|
||||
|
||||
distortionMesh[0] = createDistortionMesh(device, configuration, 0, &distortionMeshIndices, distortionUVScaleOffset[0]);
|
||||
distortionMesh[1] = createDistortionMesh(device, configuration, 1, &distortionMeshIndices, distortionUVScaleOffset[1]);
|
||||
|
||||
distortionProgram = createDistortionProgram(device);
|
||||
}
|
||||
};
|
||||
|
||||
DeviceVRGL* DeviceVRGL::createCardboard()
|
||||
{
|
||||
if (!FFlag::CardboardVR)
|
||||
return NULL;
|
||||
|
||||
CardboardVRGL* vr = new CardboardVRGL();
|
||||
|
||||
vr->configuration = gConfiguration;
|
||||
|
||||
float fovPort[4];
|
||||
computeFOVPort(vr->configuration, fovPort);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
fovPort[i] = atanf(fovPort[i]) / (3.1415926f / 180);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
eglSwapInterval(eglGetCurrentDisplay(), kUseVsync);
|
||||
#else
|
||||
(void)kUseVsync;
|
||||
#endif
|
||||
|
||||
FASTLOG3F(FLog::VR, "VR: HMD interLens %f trayToLensCenter %f screenToLens %f", vr->configuration.interLensDistance, vr->configuration.trayToLensCenterDistance, vr->configuration.screenToLensDistance);
|
||||
FASTLOG3F(FLog::VR, "VR: Lens FOV %f distortion0 %f distortion1 %f", vr->configuration.fieldOfView, vr->configuration.distortionCoeffs[0], vr->configuration.distortionCoeffs[1]);
|
||||
FASTLOG3F(FLog::VR, "VR: Screen width %f height %f border %f", vr->configuration.screenWidth, vr->configuration.screenHeight, vr->configuration.screenBorderSize);
|
||||
FASTLOG4F(FLog::VR, "VR: Eye FOV up %f down %f left %f right %f", fovPort[0], fovPort[1], fovPort[2], fovPort[3]);
|
||||
|
||||
return vr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void vrCardboardSetDeviceParams(float fieldOfView, float interLensDistance, float trayToLensCenterDistance, float screenToLensDistance, float distortionCoeff0, float distortionCoeff1)
|
||||
{
|
||||
using RBX::Graphics::gConfiguration;
|
||||
|
||||
gConfiguration.fieldOfView = fieldOfView;
|
||||
gConfiguration.interLensDistance = interLensDistance;
|
||||
gConfiguration.trayToLensCenterDistance = trayToLensCenterDistance;
|
||||
gConfiguration.screenToLensDistance = screenToLensDistance;
|
||||
gConfiguration.distortionCoeffs[0] = distortionCoeff0;
|
||||
gConfiguration.distortionCoeffs[1] = distortionCoeff1;
|
||||
}
|
||||
|
||||
void vrCardboardSetPhoneParams(int screenWidth, int screenHeight, float xdpi, float ydpi)
|
||||
{
|
||||
using RBX::Graphics::gConfiguration;
|
||||
|
||||
float metersPerInch = 0.0254f;
|
||||
float screenWidthMeters = screenWidth * (metersPerInch / xdpi);
|
||||
float screenHeightMeters = screenHeight * (metersPerInch / ydpi);
|
||||
|
||||
gConfiguration.screenWidth = std::max(screenWidthMeters, screenHeightMeters);
|
||||
gConfiguration.screenHeight = std::min(screenWidthMeters, screenHeightMeters);
|
||||
gConfiguration.screenBorderSize = 0.003f;
|
||||
}
|
||||
|
||||
void vrCardboardSetOrientation(bool flipped)
|
||||
{
|
||||
using RBX::Graphics::gScreenOrientation;
|
||||
|
||||
gScreenOrientation = flipped ? -1 : 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class ContextGL
|
||||
{
|
||||
public:
|
||||
static ContextGL* create(void* windowHandle);
|
||||
|
||||
virtual ~ContextGL() {}
|
||||
|
||||
virtual void setCurrent() = 0;
|
||||
virtual void swapBuffers() = 0;
|
||||
|
||||
virtual unsigned int getMainFramebufferId() = 0;
|
||||
virtual bool isMainFramebufferRetina() = 0;
|
||||
|
||||
virtual std::pair<unsigned int, unsigned int> updateMainFramebuffer(unsigned int width, unsigned int height) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
#include "ContextGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include "rbx/Debug.h"
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
LOGGROUP(Graphics)
|
||||
|
||||
// GL extensions
|
||||
static GLvoid (GL_APIENTRY * glBindVertexArrayPtr) (GLuint array);
|
||||
static GLvoid (GL_APIENTRY * glDeleteVertexArraysPtr) (GLsizei n, const GLuint* arrays);
|
||||
static GLvoid (GL_APIENTRY * glGenVertexArraysPtr) (GLsizei n, const GLuint* arrays);
|
||||
static GLvoid * (GL_APIENTRY * glMapBufferPtr) (GLenum target, GLenum access);
|
||||
static GLboolean (GL_APIENTRY * glUnmapBufferPtr) (GLenum target);
|
||||
static GLvoid * (GL_APIENTRY * glMapBufferRangePtr) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||
static GLvoid (GL_APIENTRY * glTexImage3DPtr) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
static GLvoid (GL_APIENTRY * glTexSubImage3DPtr) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
static GLvoid (GL_APIENTRY * glTexStorage2DPtr) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
static GLvoid (GL_APIENTRY * glTexStorage3DPtr) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
|
||||
static GLsync (GL_APIENTRY * glFenceSyncPtr) (GLenum condition, GLbitfield flags);
|
||||
static void (GL_APIENTRY * glDeleteSyncPtr) (GLsync sync);
|
||||
static GLenum (GL_APIENTRY * glClientWaitSyncPtr) (GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||
static void (GL_APIENTRY * glWaitSyncPtr) (GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||
static GLvoid (GL_APIENTRY * glBlitFramebufferPtr) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
static GLvoid (GL_APIENTRY * glRenderbufferStorageMultisamplePtr) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
static GLvoid (GL_APIENTRY * glInvalidateFramebufferPtr) (GLenum target, GLsizei numAttachments, const GLenum* attachments);
|
||||
|
||||
template <typename T> static void loadExtensionGL(T& ptr, const char* namecore, const char* nameext)
|
||||
{
|
||||
if (!ptr)
|
||||
ptr = reinterpret_cast<T>(eglGetProcAddress(namecore));
|
||||
|
||||
if (!ptr)
|
||||
ptr = reinterpret_cast<T>(eglGetProcAddress(nameext));
|
||||
}
|
||||
|
||||
#define LOAD_EXTENSION_GL(name, suffix) loadExtensionGL(name ## Ptr, #name, #name #suffix)
|
||||
|
||||
// GL stubs
|
||||
GLvoid glBindVertexArray(GLuint array)
|
||||
{
|
||||
glBindVertexArrayPtr(array);
|
||||
}
|
||||
|
||||
GLvoid glDeleteVertexArrays(GLsizei n, const GLuint *arrays)
|
||||
{
|
||||
glDeleteVertexArraysPtr(n, arrays);
|
||||
}
|
||||
|
||||
GLvoid glGenVertexArrays(GLsizei n, GLuint *arrays)
|
||||
{
|
||||
glGenVertexArraysPtr(n, arrays);
|
||||
}
|
||||
|
||||
GLvoid *glMapBuffer(GLenum target, GLenum access)
|
||||
{
|
||||
return glMapBufferPtr(target, access);
|
||||
}
|
||||
|
||||
GLboolean glUnmapBuffer(GLenum target)
|
||||
{
|
||||
return glUnmapBufferPtr(target);
|
||||
}
|
||||
|
||||
GLvoid *glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access)
|
||||
{
|
||||
return glMapBufferRangePtr(target, offset, length, access);
|
||||
}
|
||||
|
||||
GLvoid glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
|
||||
{
|
||||
glTexStorage2DPtr(target, levels, internalformat, width, height);
|
||||
}
|
||||
|
||||
GLvoid glTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
|
||||
{
|
||||
glTexImage3DPtr(target, level, internalFormat, width, height, depth, border, format, type, pixels);
|
||||
}
|
||||
|
||||
GLvoid glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels)
|
||||
{
|
||||
glTexSubImage3DPtr(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
|
||||
}
|
||||
|
||||
GLvoid glTexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)
|
||||
{
|
||||
glTexStorage3DPtr(target, levels, internalformat, width, height, depth);
|
||||
}
|
||||
|
||||
GLsync glFenceSync(GLenum condition, GLbitfield flags)
|
||||
{
|
||||
return glFenceSyncPtr(condition, flags);
|
||||
}
|
||||
|
||||
void glDeleteSync(GLsync sync)
|
||||
{
|
||||
glDeleteSyncPtr(sync);
|
||||
}
|
||||
|
||||
GLenum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout)
|
||||
{
|
||||
return glClientWaitSyncPtr(sync, flags, timeout);
|
||||
}
|
||||
|
||||
void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout)
|
||||
{
|
||||
glWaitSyncPtr(sync, flags, timeout);
|
||||
}
|
||||
|
||||
GLvoid glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
|
||||
{
|
||||
glBlitFramebufferPtr(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
|
||||
GLvoid glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)
|
||||
{
|
||||
glRenderbufferStorageMultisamplePtr(target, samples, internalformat, width, height);
|
||||
}
|
||||
|
||||
GLvoid glInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments)
|
||||
{
|
||||
glInvalidateFramebufferPtr(target, numAttachments, attachments);
|
||||
}
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class ContextGLAndroid: public ContextGL
|
||||
{
|
||||
public:
|
||||
ContextGLAndroid(void* windowHandle)
|
||||
{
|
||||
aNativeWindow = static_cast<ANativeWindow*>(windowHandle);
|
||||
|
||||
ANativeWindow_acquire(aNativeWindow);
|
||||
|
||||
FASTLOG2(FLog::Graphics, "Window size: %dx%d", ANativeWindow_getWidth(aNativeWindow), ANativeWindow_getHeight(aNativeWindow));
|
||||
|
||||
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY)
|
||||
throw runtime_error("Error creating context: eglGetDisplay %x", eglGetError());
|
||||
|
||||
if (!eglInitialize(display, 0, 0))
|
||||
throw runtime_error("Error creating context: eglInitialize %x", eglGetError());
|
||||
|
||||
EGLConfig config;
|
||||
|
||||
if (
|
||||
!tryChooseConfig(display, &config, 8, 8, 8, 24, 0) &&
|
||||
!tryChooseConfig(display, &config, 8, 8, 8, 16, 0) &&
|
||||
!tryChooseConfig(display, &config, 5, 6, 5, 16, 0) &&
|
||||
!tryChooseConfig(display, &config, 8, 8, 8, 24, 1) &&
|
||||
!tryChooseConfig(display, &config, 8, 8, 8, 16, 1) &&
|
||||
!tryChooseConfig(display, &config, 5, 6, 5, 16, 1))
|
||||
throw runtime_error("Error creating context: could not find suitable config (%x)", eglGetError());
|
||||
|
||||
surface = eglCreateWindowSurface(display, config, aNativeWindow, 0);
|
||||
|
||||
if (!surface)
|
||||
{
|
||||
throw runtime_error("Error creating context: eglCreateWindowSurface %x", eglGetError());
|
||||
}
|
||||
|
||||
static const EGLint contextAttrs[] =
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
context = eglCreateContext(display, config, NULL, contextAttrs);
|
||||
|
||||
if (!context)
|
||||
throw runtime_error("Error creating context: eglCreateContext %x", eglGetError());
|
||||
|
||||
if (!eglMakeCurrent(display, surface, surface, context))
|
||||
throw runtime_error("Error creating context: eglMakeCurrent %x", eglGetError());
|
||||
|
||||
eglQuerySurface(display, surface, EGL_WIDTH, &surfaceWidth);
|
||||
eglQuerySurface(display, surface, EGL_HEIGHT, &surfaceHeight);
|
||||
|
||||
FASTLOG4(FLog::Graphics, "Initialized EGL context %p (surface %p) with renderbuffer %dx%d", context, surface, surfaceWidth, surfaceHeight);
|
||||
|
||||
EGLint minSwapInterval = -1;
|
||||
eglGetConfigAttrib( display, config, EGL_MIN_SWAP_INTERVAL, &minSwapInterval );
|
||||
FASTLOG1(FLog::Graphics, "EGL_MIN_SWAP_INTERVAL: %d", minSwapInterval );
|
||||
|
||||
if( eglSwapInterval( display, 0 ) == EGL_FALSE )
|
||||
{
|
||||
// This should be impossible as the interval is silently clamped to EGL_MIN_SWAP_INTERVAL.
|
||||
FASTLOG(FLog::Graphics, "*** eglSwapInterval EGL_FALSE" );
|
||||
}
|
||||
|
||||
LOAD_EXTENSION_GL(glBindVertexArray, OES);
|
||||
LOAD_EXTENSION_GL(glDeleteVertexArrays, OES);
|
||||
LOAD_EXTENSION_GL(glGenVertexArrays, OES);
|
||||
LOAD_EXTENSION_GL(glMapBuffer, OES);
|
||||
LOAD_EXTENSION_GL(glUnmapBuffer, OES);
|
||||
LOAD_EXTENSION_GL(glMapBufferRange, EXT);
|
||||
LOAD_EXTENSION_GL(glTexImage3D, OES);
|
||||
LOAD_EXTENSION_GL(glTexSubImage3D, OES);
|
||||
LOAD_EXTENSION_GL(glTexStorage2D, EXT);
|
||||
LOAD_EXTENSION_GL(glTexStorage3D, EXT);
|
||||
LOAD_EXTENSION_GL(glFenceSync, EXT);
|
||||
LOAD_EXTENSION_GL(glDeleteSync, EXT);
|
||||
LOAD_EXTENSION_GL(glClientWaitSync, EXT);
|
||||
LOAD_EXTENSION_GL(glWaitSync, EXT);
|
||||
LOAD_EXTENSION_GL(glBlitFramebuffer, EXT);
|
||||
LOAD_EXTENSION_GL(glRenderbufferStorageMultisample, EXT);
|
||||
LOAD_EXTENSION_GL(glInvalidateFramebuffer, EXT);
|
||||
}
|
||||
|
||||
~ContextGLAndroid()
|
||||
{
|
||||
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
eglDestroyContext(display, context);
|
||||
eglDestroySurface(display, surface);
|
||||
|
||||
eglTerminate(display);
|
||||
|
||||
ANativeWindow_release(aNativeWindow);
|
||||
}
|
||||
|
||||
virtual void setCurrent()
|
||||
{
|
||||
bool result = eglMakeCurrent(display, surface, surface, context);
|
||||
RBXASSERT(result);
|
||||
}
|
||||
|
||||
virtual void swapBuffers()
|
||||
{
|
||||
bool result = eglSwapBuffers(display, surface);
|
||||
RBXASSERT(result);
|
||||
}
|
||||
|
||||
virtual unsigned int getMainFramebufferId()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual bool isMainFramebufferRetina()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual std::pair<unsigned int, unsigned int> updateMainFramebuffer(unsigned int width, unsigned int height)
|
||||
{
|
||||
return std::make_pair(surfaceWidth, surfaceHeight);
|
||||
}
|
||||
|
||||
private:
|
||||
ANativeWindow* aNativeWindow;
|
||||
EGLDisplay display;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
EGLint surfaceWidth;
|
||||
EGLint surfaceHeight;
|
||||
|
||||
static bool tryChooseConfig(EGLDisplay display, EGLConfig* config, int redBits, int greenBits, int blueBits, int depthBits, int swapInterval )
|
||||
{
|
||||
const EGLint attribs[] =
|
||||
{
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_MIN_SWAP_INTERVAL, swapInterval,
|
||||
EGL_RED_SIZE, redBits,
|
||||
EGL_GREEN_SIZE, greenBits,
|
||||
EGL_BLUE_SIZE, blueBits,
|
||||
EGL_DEPTH_SIZE, depthBits,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
FASTLOG4(FLog::Graphics, "Trying to choose EGL config r%d g%d b%d d%d", redBits, greenBits, blueBits, depthBits);
|
||||
|
||||
EGLint numConfigs = 0;
|
||||
return eglChooseConfig(display, attribs, config, 1, &numConfigs) && numConfigs > 0;
|
||||
}
|
||||
};
|
||||
|
||||
ContextGL* ContextGL::create(void* windowHandle)
|
||||
{
|
||||
return new ContextGLAndroid(windowHandle);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#include "ContextGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include "rbx/Debug.h"
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
|
||||
LOGGROUP(Graphics)
|
||||
|
||||
FASTFLAGVARIABLE(UpdateContextOnFollowingFrame, false)
|
||||
|
||||
FASTFLAG(GraphicsGL3)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
static NSOpenGLPixelFormat* createPixelFormatWithApi(unsigned int api)
|
||||
{
|
||||
NSOpenGLPixelFormatAttribute attribs[32];
|
||||
int ai = 0;
|
||||
|
||||
// Specify the display ID to associate the GL context with main display. Useful if there is ambiguity
|
||||
attribs[ai++] = NSOpenGLPFAScreenMask;
|
||||
attribs[ai++] = CGDisplayIDToOpenGLDisplayMask(CGMainDisplayID());
|
||||
|
||||
// Specifying "NoRecovery" gives us a context that cannot fall back to the software renderer.
|
||||
attribs[ai++] = NSOpenGLPFANoRecovery;
|
||||
|
||||
attribs[ai++] = NSOpenGLPFAAccelerated;
|
||||
attribs[ai++] = NSOpenGLPFADoubleBuffer;
|
||||
|
||||
attribs[ai++] = NSOpenGLPFAColorSize;
|
||||
attribs[ai++] = 24;
|
||||
|
||||
attribs[ai++] = NSOpenGLPFAAlphaSize;
|
||||
attribs[ai++] = 8;
|
||||
|
||||
attribs[ai++] = NSOpenGLPFAStencilSize;
|
||||
attribs[ai++] = 8;
|
||||
|
||||
attribs[ai++] = NSOpenGLPFADepthSize;
|
||||
attribs[ai++] = 16;
|
||||
|
||||
if (api)
|
||||
{
|
||||
attribs[ai++] = NSOpenGLPFAOpenGLProfile;
|
||||
attribs[ai++] = api;
|
||||
}
|
||||
|
||||
attribs[ai++] = 0;
|
||||
|
||||
return [[NSOpenGLPixelFormat alloc] initWithAttributes: attribs];
|
||||
}
|
||||
|
||||
class ContextGLNSGL: public ContextGL
|
||||
{
|
||||
public:
|
||||
ContextGLNSGL(void* windowHandle)
|
||||
: view(0)
|
||||
, pixelFormat(0)
|
||||
, context(0)
|
||||
, updateRequired(false)
|
||||
{
|
||||
view = [(NSView*)windowHandle retain];
|
||||
RBXASSERT(view);
|
||||
|
||||
if (FFlag::GraphicsGL3)
|
||||
{
|
||||
// OSX 10.7 graphics drivers crash on compiling some (valid) shaders
|
||||
if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_8)
|
||||
pixelFormat = createPixelFormatWithApi(NSOpenGLProfileVersion3_2Core);
|
||||
|
||||
if (!pixelFormat)
|
||||
pixelFormat = createPixelFormatWithApi(0);
|
||||
|
||||
if (!pixelFormat)
|
||||
throw std::runtime_error("Error creating pixel format");
|
||||
}
|
||||
else
|
||||
{
|
||||
pixelFormat = createPixelFormatWithApi(0);
|
||||
if (!pixelFormat)
|
||||
throw std::runtime_error("Error creating pixel format");
|
||||
}
|
||||
|
||||
context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
|
||||
if (!context)
|
||||
throw std::runtime_error("Error creating context");
|
||||
|
||||
GLint swapInterval = 0;
|
||||
[context setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
|
||||
|
||||
[context setView:view];
|
||||
[context makeCurrentContext];
|
||||
|
||||
if (CGLError err = CGLEnable(static_cast<CGLContextObj>([context CGLContextObj]), kCGLCEMPEngine))
|
||||
FASTLOG1(FLog::Graphics, "Error enabling MP engine: %x", err);
|
||||
|
||||
glewInitRBX();
|
||||
}
|
||||
|
||||
~ContextGLNSGL()
|
||||
{
|
||||
if ([NSOpenGLContext currentContext] == context)
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
|
||||
[context clearDrawable];
|
||||
|
||||
[context release];
|
||||
[pixelFormat release];
|
||||
[view release];
|
||||
}
|
||||
|
||||
virtual void setCurrent()
|
||||
{
|
||||
if ([NSOpenGLContext currentContext] != context)
|
||||
[context makeCurrentContext];
|
||||
}
|
||||
|
||||
virtual void swapBuffers()
|
||||
{
|
||||
[context flushBuffer];
|
||||
}
|
||||
|
||||
virtual unsigned int getMainFramebufferId()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual bool isMainFramebufferRetina()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual std::pair<unsigned int, unsigned int> updateMainFramebuffer(unsigned int width, unsigned int height)
|
||||
{
|
||||
NSRect b = [view bounds];
|
||||
|
||||
if (FFlag::UpdateContextOnFollowingFrame && updateRequired)
|
||||
{
|
||||
[context update];
|
||||
updateRequired = false;
|
||||
}
|
||||
|
||||
if (width != b.size.width || height != b.size.height)
|
||||
{
|
||||
if (!FFlag::UpdateContextOnFollowingFrame)
|
||||
[context update];
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
return std::make_pair(b.size.width, b.size.height);
|
||||
}
|
||||
|
||||
private:
|
||||
NSView* view;
|
||||
NSOpenGLPixelFormat* pixelFormat;
|
||||
NSOpenGLContext* context;
|
||||
|
||||
bool updateRequired;
|
||||
};
|
||||
|
||||
ContextGL* ContextGL::create(void* windowHandle)
|
||||
{
|
||||
return new ContextGLNSGL(windowHandle);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "ContextGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include "rbx/Debug.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#include <gl/wglew.h>
|
||||
|
||||
GLenum GLEWAPIENTRY wglewContextInit();
|
||||
|
||||
FASTFLAG(DebugGraphicsGL)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
class ContextGLWin32: public ContextGL
|
||||
{
|
||||
public:
|
||||
ContextGLWin32(void* windowHandle)
|
||||
: hwnd(reinterpret_cast<HWND>(windowHandle))
|
||||
, hdc(GetDC(hwnd))
|
||||
, hglrc(NULL)
|
||||
{
|
||||
PIXELFORMATDESCRIPTOR pfd = {};
|
||||
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
|
||||
pfd.nVersion = 1;
|
||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||
pfd.iPixelType = PFD_TYPE_RGBA;
|
||||
pfd.cColorBits = 32;
|
||||
pfd.cAlphaBits = 8;
|
||||
pfd.cDepthBits = 24;
|
||||
pfd.cStencilBits = 8;
|
||||
pfd.iLayerType = PFD_MAIN_PLANE;
|
||||
|
||||
int pf = ChoosePixelFormat(hdc, &pfd);
|
||||
|
||||
if (!pf)
|
||||
throw RBX::runtime_error("Error choosing pixel format: %x", GetLastError());
|
||||
|
||||
if (!SetPixelFormat(hdc, pf, &pfd))
|
||||
throw RBX::runtime_error("Error setting pixel format: %x", GetLastError());
|
||||
|
||||
hglrc = wglCreateContext(hdc);
|
||||
|
||||
if (!hglrc)
|
||||
throw RBX::runtime_error("Error creating context: %x", GetLastError());
|
||||
|
||||
if (!wglMakeCurrent(hdc, hglrc))
|
||||
throw RBX::runtime_error("Error changing context: %x", GetLastError());
|
||||
|
||||
if (FFlag::DebugGraphicsGL)
|
||||
{
|
||||
// We need to initialize the context with wgl function, but you can only get the wgl function after you initialize the context.
|
||||
// Let's just reinitialize it.
|
||||
wglewContextInit();
|
||||
|
||||
if (WGLEW_ARB_create_context)
|
||||
{
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
wglDeleteContext(hglrc);
|
||||
|
||||
const int attribs[4] = { WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, 0, 0 };
|
||||
|
||||
hglrc = wglCreateContextAttribsARB(hdc, NULL, attribs);
|
||||
|
||||
if (!hglrc)
|
||||
throw RBX::runtime_error("Error creating context: %x", GetLastError());
|
||||
|
||||
if (!wglMakeCurrent(hdc, hglrc))
|
||||
throw RBX::runtime_error("Error changing context: %x", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
glewInitRBX();
|
||||
|
||||
if (wglSwapIntervalEXT)
|
||||
wglSwapIntervalEXT(0);
|
||||
}
|
||||
|
||||
~ContextGLWin32()
|
||||
{
|
||||
if (wglGetCurrentContext() == hglrc)
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
wglDeleteContext(hglrc);
|
||||
}
|
||||
|
||||
virtual void setCurrent()
|
||||
{
|
||||
if (wglGetCurrentContext() != hglrc)
|
||||
{
|
||||
BOOL result = wglMakeCurrent(hdc, hglrc);
|
||||
RBXASSERT(result);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void swapBuffers()
|
||||
{
|
||||
RBXASSERT(wglGetCurrentContext() == hglrc);
|
||||
|
||||
SwapBuffers(hdc);
|
||||
}
|
||||
|
||||
virtual unsigned int getMainFramebufferId()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual bool isMainFramebufferRetina()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual std::pair<unsigned int, unsigned int> updateMainFramebuffer(unsigned int width, unsigned int height)
|
||||
{
|
||||
RECT rect = {};
|
||||
GetClientRect(hwnd, &rect);
|
||||
|
||||
return std::make_pair(rect.right - rect.left, rect.bottom - rect.top);
|
||||
}
|
||||
|
||||
private:
|
||||
HWND hwnd;
|
||||
HDC hdc;
|
||||
HGLRC hglrc;
|
||||
};
|
||||
|
||||
ContextGL* ContextGL::create(void* windowHandle)
|
||||
{
|
||||
return new ContextGLWin32(windowHandle);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
#include "ContextGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include "rbx/Debug.h"
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
LOGGROUP(Graphics)
|
||||
|
||||
FASTFLAG(GraphicsGL3)
|
||||
|
||||
// GL imports
|
||||
namespace GLES3
|
||||
{
|
||||
extern "C" GLvoid glTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
extern "C" GLvoid glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
extern "C" GLvoid glTexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
|
||||
extern "C" GLvoid glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
extern "C" GLvoid glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
extern "C" GLvoid glInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments);
|
||||
}
|
||||
|
||||
// GL stubs
|
||||
GLvoid glBindVertexArray(GLuint array)
|
||||
{
|
||||
glBindVertexArrayOES(array);
|
||||
}
|
||||
|
||||
GLvoid glDeleteVertexArrays(GLsizei n, const GLuint *arrays)
|
||||
{
|
||||
glDeleteVertexArraysOES(n, arrays);
|
||||
}
|
||||
|
||||
GLvoid glGenVertexArrays(GLsizei n, GLuint *arrays)
|
||||
{
|
||||
glGenVertexArraysOES(n, arrays);
|
||||
}
|
||||
|
||||
GLvoid *glMapBuffer(GLenum target, GLenum access)
|
||||
{
|
||||
return glMapBufferOES(target, access);
|
||||
}
|
||||
|
||||
GLboolean glUnmapBuffer(GLenum target)
|
||||
{
|
||||
return glUnmapBufferOES(target);
|
||||
}
|
||||
|
||||
GLvoid *glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access)
|
||||
{
|
||||
return glMapBufferRangeEXT(target, offset, length, access);
|
||||
}
|
||||
|
||||
GLvoid glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
|
||||
{
|
||||
glTexStorage2DEXT(target, levels, internalformat, width, height);
|
||||
}
|
||||
|
||||
GLvoid glTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
|
||||
{
|
||||
GLES3::glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
|
||||
}
|
||||
|
||||
GLvoid glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels)
|
||||
{
|
||||
GLES3::glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
|
||||
}
|
||||
|
||||
GLvoid glTexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)
|
||||
{
|
||||
GLES3::glTexStorage3D(target, levels, internalformat, width, height, depth);
|
||||
}
|
||||
|
||||
GLsync glFenceSync(GLenum condition, GLbitfield flags)
|
||||
{
|
||||
return glFenceSyncAPPLE(condition, flags);
|
||||
}
|
||||
|
||||
void glDeleteSync(GLsync sync)
|
||||
{
|
||||
glDeleteSyncAPPLE(sync);
|
||||
}
|
||||
|
||||
GLenum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout)
|
||||
{
|
||||
return glClientWaitSyncAPPLE(sync, flags, timeout);
|
||||
}
|
||||
|
||||
void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout)
|
||||
{
|
||||
glWaitSyncAPPLE(sync, flags, timeout);
|
||||
}
|
||||
|
||||
GLvoid glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
|
||||
{
|
||||
GLES3::glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
|
||||
GLvoid glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)
|
||||
{
|
||||
GLES3::glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
|
||||
}
|
||||
|
||||
GLvoid glInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments)
|
||||
{
|
||||
GLES3::glInvalidateFramebuffer(target, numAttachments, attachments);
|
||||
}
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class ContextGLEAGL: public ContextGL
|
||||
{
|
||||
public:
|
||||
ContextGLEAGL(void* windowHandle)
|
||||
{
|
||||
view = [(UIView*)windowHandle retain];
|
||||
RBXASSERT(view);
|
||||
RBXASSERT([view isKindOfClass:[UIView class]]);
|
||||
|
||||
layer = [(CAEAGLLayer*)view.layer retain];
|
||||
RBXASSERT(layer);
|
||||
RBXASSERT([layer isKindOfClass:[CAEAGLLayer class]]);
|
||||
|
||||
// Create context
|
||||
if (FFlag::GraphicsGL3)
|
||||
{
|
||||
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
||||
|
||||
if (!context)
|
||||
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
||||
|
||||
if (!context)
|
||||
throw std::runtime_error("Error creating context");
|
||||
}
|
||||
else
|
||||
{
|
||||
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
||||
if (!context)
|
||||
throw std::runtime_error("Error creating context");
|
||||
}
|
||||
|
||||
// Enable Retina if available
|
||||
if (isMainFramebufferRetina())
|
||||
view.contentScaleFactor = 2.0;
|
||||
|
||||
// Perform basic layer configuration
|
||||
[layer setOpaque:YES];
|
||||
[layer setDrawableProperties: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
|
||||
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]];
|
||||
|
||||
if (![EAGLContext setCurrentContext:context])
|
||||
throw std::runtime_error("Error changing context");
|
||||
|
||||
// Create main color/depth buffer
|
||||
glGenRenderbuffers(1, &colorRenderbufferId);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbufferId);
|
||||
|
||||
if (![context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer])
|
||||
throw std::runtime_error("Error allocating main renderbuffer");
|
||||
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &renderbufferWidth);
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &renderbufferHeight);
|
||||
|
||||
FASTLOG4(FLog::Graphics, "Initialized EAGLContext %p (view %p) with renderbuffer %dx%d", context, view, renderbufferWidth, renderbufferHeight);
|
||||
|
||||
glGenRenderbuffers(1, &depthRenderbufferId);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbufferId);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, renderbufferWidth, renderbufferHeight);
|
||||
|
||||
// Create main framebuffer
|
||||
glGenFramebuffers(1, &framebufferId);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebufferId);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbufferId);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbufferId);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRenderbufferId);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE)
|
||||
throw RBX::runtime_error("Unsupported main framebuffer configuration: error %x", status);
|
||||
}
|
||||
|
||||
~ContextGLEAGL()
|
||||
{
|
||||
setCurrent();
|
||||
|
||||
glDeleteFramebuffers(1, &framebufferId);
|
||||
glDeleteRenderbuffers(1, &colorRenderbufferId);
|
||||
glDeleteRenderbuffers(1, &depthRenderbufferId);
|
||||
|
||||
[EAGLContext setCurrentContext:nil];
|
||||
|
||||
[context release];
|
||||
[layer release];
|
||||
[view release];
|
||||
}
|
||||
|
||||
virtual void setCurrent()
|
||||
{
|
||||
if ([EAGLContext currentContext] != context)
|
||||
{
|
||||
bool result = [EAGLContext setCurrentContext:context];
|
||||
RBXASSERT(result);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void swapBuffers()
|
||||
{
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbufferId);
|
||||
[context presentRenderbuffer:GL_RENDERBUFFER];
|
||||
}
|
||||
|
||||
virtual unsigned int getMainFramebufferId()
|
||||
{
|
||||
return framebufferId;
|
||||
}
|
||||
|
||||
virtual bool isMainFramebufferRetina()
|
||||
{
|
||||
// We can render with Retina on all devices that support Retina with iOS 6+ and GLES3
|
||||
// iOS 5.1 has a non-conforming OpenGL ES implementation that does not allow us to render into NPOT targets
|
||||
return
|
||||
[[UIScreen mainScreen] scale] >= 2.0 &&
|
||||
[[[UIDevice currentDevice] systemVersion] compare:@"6.0" options:NSNumericSearch] != NSOrderedAscending &&
|
||||
context.API >= kEAGLRenderingAPIOpenGLES3;
|
||||
}
|
||||
|
||||
virtual std::pair<unsigned int, unsigned int> updateMainFramebuffer(unsigned int width, unsigned int height)
|
||||
{
|
||||
return std::make_pair(renderbufferWidth, renderbufferHeight);
|
||||
}
|
||||
|
||||
private:
|
||||
UIView* view;
|
||||
CAEAGLLayer* layer;
|
||||
EAGLContext* context;
|
||||
|
||||
unsigned int colorRenderbufferId;
|
||||
unsigned int depthRenderbufferId;
|
||||
unsigned int framebufferId;
|
||||
|
||||
int renderbufferWidth;
|
||||
int renderbufferHeight;
|
||||
};
|
||||
|
||||
ContextGL* ContextGL::create(void* windowHandle)
|
||||
{
|
||||
return new ContextGLEAGL(windowHandle);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,477 @@
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "GeometryGL.h"
|
||||
#include "ShaderGL.h"
|
||||
#include "TextureGL.h"
|
||||
#include "FramebufferGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
FASTFLAGVARIABLE(GraphicsGLUseDiscard, false)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
static const GLenum gCullModeGL[RasterizerState::Cull_Count] =
|
||||
{
|
||||
GL_NONE,
|
||||
GL_BACK,
|
||||
GL_FRONT
|
||||
};
|
||||
|
||||
struct BlendFuncGL
|
||||
{
|
||||
GLenum src, dst;
|
||||
};
|
||||
|
||||
static const GLenum gBlendFactorsGL[BlendState::Factor_Count] =
|
||||
{
|
||||
GL_ONE,
|
||||
GL_ZERO,
|
||||
GL_DST_COLOR,
|
||||
GL_SRC_ALPHA,
|
||||
GL_ONE_MINUS_SRC_ALPHA,
|
||||
GL_DST_ALPHA,
|
||||
GL_ONE_MINUS_DST_ALPHA
|
||||
};
|
||||
|
||||
static const GLenum gDepthFuncGL[DepthState::Function_Count] =
|
||||
{
|
||||
GL_ALWAYS,
|
||||
GL_LESS,
|
||||
GL_LEQUAL
|
||||
};
|
||||
|
||||
DeviceContextGL::DeviceContextGL(DeviceGL* dev)
|
||||
: globalDataVersion(0)
|
||||
, device(dev)
|
||||
, defaultAnisotropy(1)
|
||||
, cachedProgram(0)
|
||||
, cachedRasterizerState(RasterizerState::Cull_None)
|
||||
, cachedBlendState(BlendState::Mode_None)
|
||||
, cachedDepthState(DepthState::Function_Always, false)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAYSIZE(cachedTextures); ++i)
|
||||
cachedTextures[i] = NULL;
|
||||
}
|
||||
|
||||
DeviceContextGL::~DeviceContextGL()
|
||||
{
|
||||
}
|
||||
|
||||
void DeviceContextGL::clearStates()
|
||||
{
|
||||
// Clear framebuffer cache
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
// Clear program cache
|
||||
cachedProgram = 0;
|
||||
glUseProgram(0);
|
||||
|
||||
// Clear texture cache
|
||||
for (size_t i = 0; i < ARRAYSIZE(cachedTextures); ++i)
|
||||
cachedTextures[i] = NULL;
|
||||
|
||||
// Clear states to invalid values to guarantee a cache miss on the next setup
|
||||
cachedRasterizerState = RasterizerState(RasterizerState::Cull_Count);
|
||||
cachedBlendState = BlendState(BlendState::Mode_Count);
|
||||
cachedDepthState = DepthState(DepthState::Function_Count, false);
|
||||
|
||||
// Setup state we never touch once
|
||||
#ifndef GLES
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DeviceContextGL::invalidateCachedProgram()
|
||||
{
|
||||
cachedProgram = 0;
|
||||
}
|
||||
|
||||
void DeviceContextGL::invalidateCachedTexture(Texture* texture)
|
||||
{
|
||||
for (unsigned int stage = 0; stage < ARRAYSIZE(cachedTextures); ++stage)
|
||||
if (cachedTextures[stage] == texture)
|
||||
cachedTextures[stage] = NULL;
|
||||
}
|
||||
|
||||
void DeviceContextGL::invalidateCachedTextureStage(unsigned int stage)
|
||||
{
|
||||
RBXASSERT(stage < ARRAYSIZE(cachedTextures));
|
||||
cachedTextures[stage] = NULL;
|
||||
}
|
||||
|
||||
void DeviceContextGL::defineGlobalConstants(size_t dataSize)
|
||||
{
|
||||
RBXASSERT(globalData.empty());
|
||||
RBXASSERT(dataSize > 0);
|
||||
|
||||
globalData.resize(dataSize);
|
||||
}
|
||||
|
||||
void DeviceContextGL::setDefaultAnisotropy(unsigned int value)
|
||||
{
|
||||
defaultAnisotropy = value;
|
||||
}
|
||||
|
||||
void DeviceContextGL::updateGlobalConstants(const void* data, size_t dataSize)
|
||||
{
|
||||
RBXASSERT(dataSize == globalData.size());
|
||||
|
||||
memcpy(&globalData[0], data, dataSize);
|
||||
globalDataVersion++;
|
||||
}
|
||||
|
||||
void DeviceContextGL::bindFramebuffer(Framebuffer* buffer)
|
||||
{
|
||||
unsigned int drawId = static_cast<FramebufferGL*>(buffer)->getId();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, drawId);
|
||||
glViewport(0, 0, buffer->getWidth(), buffer->getHeight());
|
||||
|
||||
#ifndef GLES
|
||||
unsigned int drawBuffers = static_cast<FramebufferGL*>(buffer)->getDrawBuffers();
|
||||
|
||||
if (drawId == 0)
|
||||
{
|
||||
glDrawBuffer(GL_BACK);
|
||||
}
|
||||
else if (glDrawBuffers)
|
||||
{
|
||||
GLenum buffers[16];
|
||||
RBXASSERT(drawBuffers < ARRAYSIZE(buffers));
|
||||
|
||||
for (unsigned int i = 0; i < drawBuffers; ++i)
|
||||
buffers[i] = GL_COLOR_ATTACHMENT0 + i;
|
||||
|
||||
glDrawBuffers(drawBuffers, buffers);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DeviceContextGL::clearFramebuffer(unsigned int mask, const float color[4], float depth, unsigned int stencil)
|
||||
{
|
||||
unsigned int maskGl = 0;
|
||||
|
||||
if (mask & Buffer_Color)
|
||||
{
|
||||
// Need color writes for color clear to work
|
||||
setBlendState(BlendState(BlendState::Mode_None, BlendState::Color_All));
|
||||
|
||||
maskGl |= GL_COLOR_BUFFER_BIT;
|
||||
glClearColor(color[0], color[1], color[2], color[3]);
|
||||
}
|
||||
|
||||
if (mask & Buffer_Depth)
|
||||
{
|
||||
// Need depth writes for depth clear to work
|
||||
setDepthState(DepthState(DepthState::Function_Always, true));
|
||||
|
||||
maskGl |= GL_DEPTH_BUFFER_BIT;
|
||||
glClearDepth(depth);
|
||||
}
|
||||
|
||||
if (mask & Buffer_Stencil)
|
||||
{
|
||||
// Need stencil writes for stencil clear to work
|
||||
glStencilMask(~0u);
|
||||
|
||||
maskGl |= GL_STENCIL_BUFFER_BIT;
|
||||
glClearStencil(stencil);
|
||||
}
|
||||
|
||||
RBXASSERT(maskGl);
|
||||
glClear(maskGl);
|
||||
}
|
||||
|
||||
void DeviceContextGL::copyFramebuffer(Framebuffer* buffer, Texture* texture)
|
||||
{
|
||||
RBXASSERT(texture->getType() == Texture::Type_2D);
|
||||
RBXASSERT(buffer->getWidth() == texture->getWidth() && buffer->getHeight() == texture->getHeight());
|
||||
|
||||
invalidateCachedTextureStage(0);
|
||||
|
||||
GLint oldfb = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldfb);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<FramebufferGL*>(buffer)->getId());
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, static_cast<TextureGL*>(texture)->getId());
|
||||
|
||||
#ifndef GLES
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
#endif
|
||||
|
||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texture->getWidth(), texture->getHeight());
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldfb);
|
||||
}
|
||||
|
||||
void DeviceContextGL::resolveFramebuffer(Framebuffer* msaaBuffer, Framebuffer* buffer, unsigned int mask)
|
||||
{
|
||||
RBXASSERT(msaaBuffer->getSamples() > 1);
|
||||
RBXASSERT(buffer->getSamples() == 1);
|
||||
RBXASSERT(msaaBuffer->getWidth() == buffer->getWidth() && msaaBuffer->getHeight() == buffer->getHeight());
|
||||
|
||||
GLint oldfb = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldfb);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<FramebufferGL*>(msaaBuffer)->getId());
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<FramebufferGL*>(buffer)->getId());
|
||||
|
||||
if (mask & Buffer_Color)
|
||||
{
|
||||
#ifndef GLES
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
#endif
|
||||
|
||||
glBlitFramebuffer(0, 0, buffer->getWidth(), buffer->getHeight(), 0, 0, buffer->getWidth(), buffer->getHeight(), GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
}
|
||||
|
||||
if (mask & (Buffer_Depth | Buffer_Stencil))
|
||||
{
|
||||
unsigned int maskGl = 0;
|
||||
|
||||
if (mask & Buffer_Depth)
|
||||
maskGl |= GL_DEPTH_BUFFER_BIT;
|
||||
|
||||
if (mask & Buffer_Stencil)
|
||||
maskGl |= GL_STENCIL_BUFFER_BIT;
|
||||
|
||||
glBlitFramebuffer(0, 0, buffer->getWidth(), buffer->getHeight(), 0, 0, buffer->getWidth(), buffer->getHeight(), maskGl, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldfb);
|
||||
}
|
||||
|
||||
void DeviceContextGL::discardFramebuffer(Framebuffer* buffer, unsigned int mask)
|
||||
{
|
||||
if (!device->getCapsGL().ext3)
|
||||
return;
|
||||
|
||||
if (!FFlag::GraphicsGLUseDiscard)
|
||||
return;
|
||||
|
||||
unsigned int drawBuffers = static_cast<FramebufferGL*>(buffer)->getDrawBuffers();
|
||||
|
||||
GLenum attachments[16];
|
||||
unsigned int index = 0;
|
||||
|
||||
if (mask & Buffer_Color)
|
||||
for (unsigned int i = 0; i < drawBuffers; ++i)
|
||||
attachments[index++] = GL_COLOR_ATTACHMENT0 + i;
|
||||
|
||||
if (mask & Buffer_Depth)
|
||||
attachments[index++] = GL_DEPTH_ATTACHMENT;
|
||||
|
||||
if (mask & Buffer_Stencil)
|
||||
attachments[index++] = GL_STENCIL_ATTACHMENT;
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
GLint oldfb = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldfb);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<FramebufferGL*>(buffer)->getId());
|
||||
|
||||
glInvalidateFramebuffer(GL_FRAMEBUFFER, index, attachments);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldfb);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceContextGL::bindProgram(ShaderProgram* program)
|
||||
{
|
||||
static_cast<ShaderProgramGL*>(program)->bind(&globalData[0], globalDataVersion, &cachedProgram);
|
||||
}
|
||||
|
||||
void DeviceContextGL::setWorldTransforms4x3(const float* data, size_t matrixCount)
|
||||
{
|
||||
cachedProgram->setWorldTransforms4x3(data, matrixCount);
|
||||
}
|
||||
|
||||
void DeviceContextGL::setConstant(int handle, const float* data, size_t vectorCount)
|
||||
{
|
||||
cachedProgram->setConstant(handle, data, vectorCount);
|
||||
}
|
||||
|
||||
void DeviceContextGL::bindTexture(unsigned int stage, Texture* texture, const SamplerState& state)
|
||||
{
|
||||
SamplerState realState =
|
||||
(state.getFilter() == SamplerState::Filter_Anisotropic && state.getAnisotropy() == 0)
|
||||
? SamplerState(state.getFilter(), state.getAddress(), defaultAnisotropy)
|
||||
: state;
|
||||
|
||||
RBXASSERT(stage < device->getCaps().maxTextureUnits);
|
||||
RBXASSERT(stage < ARRAYSIZE(cachedTextures));
|
||||
|
||||
static_cast<TextureGL*>(texture)->bind(stage, realState, &cachedTextures[stage]);
|
||||
}
|
||||
|
||||
void DeviceContextGL::setRasterizerState(const RasterizerState& state)
|
||||
{
|
||||
if (cachedRasterizerState != state)
|
||||
{
|
||||
cachedRasterizerState = state;
|
||||
|
||||
if (state.getCullMode() == RasterizerState::Cull_None)
|
||||
{
|
||||
glDisable(GL_CULL_FACE);
|
||||
}
|
||||
else
|
||||
{
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(gCullModeGL[state.getCullMode()]);
|
||||
}
|
||||
|
||||
if (state.getDepthBias() == 0)
|
||||
{
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
}
|
||||
else
|
||||
{
|
||||
float bias = static_cast<float>(state.getDepthBias());
|
||||
float slopeBias = bias / 32.f; // do we need explicit control over slope or just a better formula since these numbers are magic anyway?
|
||||
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
glPolygonOffset(slopeBias, bias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceContextGL::setBlendState(const BlendState& state)
|
||||
{
|
||||
if (cachedBlendState != state)
|
||||
{
|
||||
cachedBlendState = state;
|
||||
|
||||
unsigned int colorMask = state.getColorMask();
|
||||
|
||||
glColorMask((colorMask & BlendState::Color_R) != 0, (colorMask & BlendState::Color_G) != 0, (colorMask & BlendState::Color_B) != 0, (colorMask & BlendState::Color_A) != 0);
|
||||
|
||||
if (!state.blendingNeeded())
|
||||
{
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
else
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
if (!state.separateAlphaBlend())
|
||||
glBlendFunc(gBlendFactorsGL[state.getColorSrc()], gBlendFactorsGL[state.getColorDst()]);
|
||||
else
|
||||
glBlendFuncSeparate(gBlendFactorsGL[state.getColorSrc()], gBlendFactorsGL[state.getColorDst()], gBlendFactorsGL[state.getAlphaSrc()], gBlendFactorsGL[state.getAlphaDst()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceContextGL::setDepthState(const DepthState& state)
|
||||
{
|
||||
if (cachedDepthState != state)
|
||||
{
|
||||
cachedDepthState = state;
|
||||
|
||||
if (state.getFunction() == DepthState::Function_Always && !state.getWrite())
|
||||
{
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
else
|
||||
{
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(gDepthFuncGL[state.getFunction()]);
|
||||
glDepthMask(state.getWrite());
|
||||
}
|
||||
|
||||
switch (state.getStencilMode())
|
||||
{
|
||||
case DepthState::Stencil_None:
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
break;
|
||||
|
||||
case DepthState::Stencil_IsNotZero:
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilFunc(GL_NOTEQUAL, 0, ~0u);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||
break;
|
||||
|
||||
case DepthState::Stencil_UpdateZFail:
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilFunc(GL_ALWAYS, 0, ~0u);
|
||||
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR_WRAP, GL_KEEP);
|
||||
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
|
||||
break;
|
||||
|
||||
default:
|
||||
RBXASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceContextGL::drawImpl(Geometry* geometry, Geometry::Primitive primitive, unsigned int offset, unsigned int count, unsigned int indexRangeBegin, unsigned int indexRangeEnd)
|
||||
{
|
||||
static_cast<GeometryGL*>(geometry)->draw(primitive, offset, count);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Markers:
|
||||
|
||||
void DeviceContextGL::pushDebugMarkerGroup(const char* text)
|
||||
{
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
if (device->getCapsGL().extDebugMarkers)
|
||||
{
|
||||
glPushGroupMarkerEXT(0, text);
|
||||
}
|
||||
#elif defined(__ANDROID__)
|
||||
;
|
||||
#else
|
||||
if (glPushDebugGroup) // Requires GL4.3, because ARB-version does not have marker enums for type
|
||||
{
|
||||
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION_ARB, 0, -1, text );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DeviceContextGL::popDebugMarkerGroup()
|
||||
{
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
if (device->getCapsGL().extDebugMarkers)
|
||||
{
|
||||
glPopGroupMarkerEXT();
|
||||
}
|
||||
#elif defined(__ANDROID__)
|
||||
;
|
||||
#else
|
||||
if (glPopDebugGroup) // Requires GL4.3, because ARB-version does not have marker enums for type
|
||||
{
|
||||
glPopDebugGroup();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DeviceContextGL::setDebugMarker(const char* text)
|
||||
{
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
if (device->getCapsGL().extDebugMarkers)
|
||||
{
|
||||
glInsertEventMarkerEXT(0, text);
|
||||
}
|
||||
#elif defined(__ANDROID__)
|
||||
;
|
||||
#else
|
||||
if (glDebugMessageInsert) // Requires GL4.3, because ARB-version does not have marker enums for type
|
||||
{
|
||||
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION_ARB, GL_DEBUG_TYPE_MARKER, 0, GL_DEBUG_SEVERITY_NOTIFICATION, -1, text );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "GeometryGL.h"
|
||||
#include "ShaderGL.h"
|
||||
#include "TextureGL.h"
|
||||
#include "FramebufferGL.h"
|
||||
|
||||
#include "ContextGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include "rbx/Profiler.h"
|
||||
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
LOGGROUP(Graphics)
|
||||
|
||||
FASTFLAGVARIABLE(DebugGraphicsGL, false)
|
||||
|
||||
FASTFLAGVARIABLE(GraphicsGL3, false)
|
||||
|
||||
FASTFLAG(RenderVR)
|
||||
|
||||
FASTFLAGVARIABLE(GraphicsGLReduceLatency, false)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
static std::set<std::string> getExtensions()
|
||||
{
|
||||
std::set<std::string> result;
|
||||
|
||||
if (const char* extensionString = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)))
|
||||
{
|
||||
std::istringstream ext(extensionString);
|
||||
|
||||
std::string str;
|
||||
while (ext >> str)
|
||||
result.insert(str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef GLES
|
||||
if (FFlag::GraphicsGL3 && GLEW_VERSION_3_0)
|
||||
{
|
||||
int extensionCount = 0;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount);
|
||||
|
||||
for (int i = 0; i < extensionCount; ++i)
|
||||
result.insert(reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i)));
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef GLES
|
||||
static DeviceCapsGL createDeviceCaps(ContextGL* context, const std::set<std::string>& extensions)
|
||||
{
|
||||
const char* renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
||||
const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||
|
||||
bool version3 = FFlag::GraphicsGL3 && strncmp(version, "OpenGL ES ", 10) == 0 && version[10] >= '3';
|
||||
|
||||
DeviceCapsGL caps;
|
||||
|
||||
GLint texSize;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize);
|
||||
|
||||
GLint stencilBits;
|
||||
glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
|
||||
|
||||
caps.supportsFramebuffer = true;
|
||||
caps.supportsShaders = true;
|
||||
caps.supportsFFP = false;
|
||||
caps.supportsStencil = stencilBits >= 8;
|
||||
caps.supportsIndex32 = version3 || extensions.count("GL_OES_element_index_uint");
|
||||
|
||||
caps.supportsTextureDXT = false;
|
||||
caps.supportsTexturePVR = extensions.count("GL_IMG_texture_compression_pvrtc");
|
||||
caps.supportsTextureHalfFloat = version3 || extensions.count("GL_OES_texture_half_float");
|
||||
caps.supportsTexture3D = version3;
|
||||
caps.supportsTextureNPOT = version3;
|
||||
caps.supportsTextureETC1 = extensions.count("GL_OES_compressed_ETC1_RGB8_texture");
|
||||
|
||||
caps.supportsTexturePartialMipChain = version3 || extensions.count("GL_APPLE_texture_max_level") || (!FFlag::GraphicsGL3 && strstr(version, "OpenGL ES 3"));
|
||||
|
||||
caps.maxDrawBuffers = 1;
|
||||
|
||||
if (version3)
|
||||
{
|
||||
GLint samples = 1;
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &samples);
|
||||
|
||||
caps.maxSamples = samples;
|
||||
}
|
||||
else
|
||||
{
|
||||
caps.maxSamples = 1;
|
||||
}
|
||||
|
||||
caps.maxTextureSize = texSize;
|
||||
caps.maxTextureUnits = version3 ? 16 : 8;
|
||||
|
||||
caps.colorOrderBGR = false;
|
||||
caps.needsHalfPixelOffset = false;
|
||||
caps.requiresRenderTargetFlipping = true;
|
||||
|
||||
caps.retina = context->isMainFramebufferRetina();
|
||||
|
||||
caps.ext3 = version3;
|
||||
|
||||
caps.extVertexArrayObject = version3 || (extensions.count("GL_OES_vertex_array_object") && !strstr(renderer, "Adreno"));
|
||||
caps.extTextureStorage = version3 || extensions.count("GL_EXT_texture_storage");
|
||||
caps.extMapBuffer = version3 || extensions.count("GL_OES_mapbuffer");
|
||||
caps.extMapBufferRange = version3 || extensions.count("GL_EXT_map_buffer_range");
|
||||
caps.extTimerQuery = false;
|
||||
caps.extDebugMarkers = extensions.count("GL_EXT_debug_marker");
|
||||
caps.extSync = version3;
|
||||
|
||||
return caps;
|
||||
}
|
||||
#else
|
||||
static DeviceCapsGL createDeviceCapsOld(ContextGL* context)
|
||||
{
|
||||
DeviceCapsGL caps;
|
||||
|
||||
GLint texSize = 0;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize);
|
||||
|
||||
GLint stencilBits = 0;
|
||||
glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
|
||||
|
||||
caps.supportsFramebuffer = GLEW_VERSION_3_0 || GLEW_ARB_framebuffer_object || GLEW_EXT_framebuffer_object;
|
||||
caps.supportsShaders = GLEW_VERSION_2_0 || (GLEW_ARB_shading_language_100 && GLEW_ARB_shader_objects && GLEW_ARB_fragment_shader && GLEW_ARB_vertex_shader);
|
||||
caps.supportsFFP = false;
|
||||
caps.supportsStencil = GLEW_VERSION_2_0 && stencilBits >= 8; // we need full two-sided stencil support
|
||||
caps.supportsIndex32 = true;
|
||||
|
||||
caps.supportsTextureDXT = (GLEW_VERSION_1_3 || GLEW_ARB_texture_compression) && GLEW_EXT_texture_compression_s3tc;
|
||||
caps.supportsTexturePVR = false;
|
||||
caps.supportsTextureHalfFloat = !!GLEW_ARB_half_float_pixel;
|
||||
caps.supportsTexture3D = GLEW_VERSION_1_2 || GLEW_EXT_texture3D;
|
||||
caps.supportsTextureETC1 = false;
|
||||
|
||||
caps.supportsTexturePartialMipChain = true;
|
||||
|
||||
// According to http://aras-p.info/blog/2012/10/17/non-power-of-two-textures/, GL extensions lie
|
||||
caps.supportsTextureNPOT = GLEW_ARB_texture_non_power_of_two && texSize >= 8192;
|
||||
|
||||
if (caps.supportsFramebuffer && (GLEW_VERSION_3_0 || GLEW_ARB_draw_buffers || GLEW_ATI_draw_buffers))
|
||||
{
|
||||
GLint drawBuffers = 0;
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &drawBuffers);
|
||||
|
||||
caps.maxDrawBuffers = drawBuffers;
|
||||
}
|
||||
else
|
||||
{
|
||||
caps.maxDrawBuffers = 1;
|
||||
}
|
||||
|
||||
if (caps.supportsFramebuffer && (GLEW_VERSION_3_3 || GLEW_EXT_framebuffer_multisample))
|
||||
{
|
||||
GLint samples = 0;
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &samples);
|
||||
|
||||
caps.maxSamples = samples;
|
||||
}
|
||||
else
|
||||
{
|
||||
caps.maxSamples = 1;
|
||||
}
|
||||
|
||||
caps.maxTextureSize = texSize;
|
||||
caps.maxTextureUnits = 16;
|
||||
|
||||
caps.colorOrderBGR = false;
|
||||
caps.needsHalfPixelOffset = false;
|
||||
caps.requiresRenderTargetFlipping = true;
|
||||
|
||||
caps.retina = context->isMainFramebufferRetina();
|
||||
|
||||
caps.ext3 = false;
|
||||
|
||||
caps.extVertexArrayObject = (GLEW_VERSION_3_0 || GLEW_ARB_vertex_array_object || GLEW_APPLE_vertex_array_object);
|
||||
caps.extTextureStorage = (GLEW_VERSION_4_2 || GLEW_ARB_texture_storage);
|
||||
caps.extMapBuffer = true;
|
||||
caps.extMapBufferRange = (GLEW_VERSION_3_0 || GLEW_ARB_map_buffer_range);
|
||||
caps.extTimerQuery = !!GLEW_EXT_timer_query;
|
||||
caps.extDebugMarkers = false;
|
||||
caps.extSync = (GLEW_VERSION_3_2 || GLEW_ARB_sync);
|
||||
|
||||
#ifdef _WIN32
|
||||
const char* vendorString = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
||||
|
||||
if (strstr(vendorString, "Intel"))
|
||||
{
|
||||
// Intel drivers don't store IB state as part of VAO (http://stackoverflow.com/questions/8973690/vao-and-element-array-buffer-state)
|
||||
caps.extVertexArrayObject = false;
|
||||
}
|
||||
|
||||
if (strstr(vendorString, "ATI Technologies"))
|
||||
{
|
||||
// Some AMD drivers can't create cubemaps with TS and can create 3D textures that they can't update afterwards...
|
||||
caps.extTextureStorage = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
static DeviceCapsGL createDeviceCaps(ContextGL* context, const std::set<std::string>& extensions)
|
||||
{
|
||||
if (!FFlag::GraphicsGL3)
|
||||
return createDeviceCapsOld(context);
|
||||
|
||||
const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||
|
||||
bool version3 = isdigit(version[0]) && version[0] >= '3';
|
||||
|
||||
DeviceCapsGL caps;
|
||||
|
||||
GLint texSize = 0;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize);
|
||||
|
||||
GLint stencilBits = 0;
|
||||
glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
|
||||
|
||||
caps.supportsFramebuffer = version3 || (extensions.count("GL_ARB_framebuffer_object") || extensions.count("GL_EXT_framebuffer_object"));
|
||||
caps.supportsShaders =
|
||||
version3 || GLEW_VERSION_2_0 ||
|
||||
// need this for Mac OpenGL 1.4 :-/
|
||||
(extensions.count("GL_ARB_shading_language_100") && extensions.count("GL_ARB_shader_objects") && extensions.count("GL_ARB_fragment_shader") && extensions.count("GL_ARB_vertex_shader"));
|
||||
|
||||
caps.supportsFFP = false;
|
||||
caps.supportsStencil = GLEW_VERSION_2_0 && stencilBits >= 8; // we need full two-sided stencil support
|
||||
caps.supportsIndex32 = true;
|
||||
|
||||
caps.supportsTextureDXT = !!extensions.count("GL_EXT_texture_compression_s3tc");
|
||||
caps.supportsTexturePVR = false;
|
||||
caps.supportsTextureHalfFloat = version3;
|
||||
caps.supportsTexture3D = true;
|
||||
caps.supportsTextureETC1 = false;
|
||||
|
||||
caps.supportsTexturePartialMipChain = true;
|
||||
|
||||
caps.supportsTextureNPOT = version3;
|
||||
|
||||
if (version3)
|
||||
{
|
||||
GLint drawBuffers = 0;
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &drawBuffers);
|
||||
|
||||
GLint samples = 0;
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &samples);
|
||||
|
||||
caps.maxDrawBuffers = drawBuffers;
|
||||
caps.maxSamples = samples;
|
||||
}
|
||||
else
|
||||
{
|
||||
caps.maxDrawBuffers = 1;
|
||||
caps.maxSamples = 1;
|
||||
}
|
||||
|
||||
caps.maxTextureSize = texSize;
|
||||
caps.maxTextureUnits = 16;
|
||||
|
||||
caps.colorOrderBGR = false;
|
||||
caps.needsHalfPixelOffset = false;
|
||||
caps.requiresRenderTargetFlipping = true;
|
||||
|
||||
caps.retina = context->isMainFramebufferRetina();
|
||||
|
||||
caps.ext3 = version3;
|
||||
|
||||
caps.extVertexArrayObject = version3;
|
||||
caps.extTextureStorage = GLEW_VERSION_4_2 || (version3 && extensions.count("GL_ARB_texture_storage"));
|
||||
caps.extMapBuffer = true;
|
||||
caps.extMapBufferRange = version3;
|
||||
caps.extTimerQuery = !!GLEW_VERSION_3_3;
|
||||
caps.extDebugMarkers = false;
|
||||
caps.extSync = !!GLEW_VERSION_3_2;
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
static void GLAPIENTRY debugOutputGLARB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const GLvoid* userParam)
|
||||
{
|
||||
FASTLOGS(FLog::Graphics, "GL Debug: %s", message);
|
||||
}
|
||||
#endif
|
||||
|
||||
static DeviceVRGL* createVR()
|
||||
{
|
||||
#if defined(RBX_PLATFORM_IOS) || defined(__ANDROID__)
|
||||
if (DeviceVRGL* result = DeviceVRGL::createCardboard())
|
||||
return result;
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DeviceGL::DeviceGL(void* windowHandle)
|
||||
: frameTimeQueryId(0)
|
||||
, frameTimeQueryIssued(false)
|
||||
, frameEventQueryId(0)
|
||||
, frameEventQueryIssued(false)
|
||||
, gpuTime(0)
|
||||
, vrEnabled(true)
|
||||
{
|
||||
glContext.reset(ContextGL::create(windowHandle));
|
||||
|
||||
if (FFlag::RenderVR)
|
||||
vr.reset(createVR());
|
||||
|
||||
std::set<std::string> extensions = getExtensions();
|
||||
|
||||
caps = createDeviceCaps(glContext.get(), extensions);
|
||||
|
||||
FASTLOGS(FLog::Graphics, "GL Renderer: %s", reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||
FASTLOGS(FLog::Graphics, "GL Version: %s", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
|
||||
FASTLOGS(FLog::Graphics, "GL Vendor: %s", reinterpret_cast<const char*>(glGetString(GL_VENDOR)));
|
||||
|
||||
if (caps.supportsShaders)
|
||||
FASTLOGS(FLog::Graphics, "GLSL version: %s", reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)));
|
||||
|
||||
std::string extensionString;
|
||||
|
||||
for (auto& e: extensions)
|
||||
{
|
||||
extensionString += " ";
|
||||
extensionString += e;
|
||||
}
|
||||
|
||||
while (!extensionString.empty())
|
||||
{
|
||||
std::string::size_type space = extensionString.find_first_of(' ', 128);
|
||||
FASTLOGS(FLog::Graphics, "Ext:%s", extensionString.substr(0, space));
|
||||
extensionString.erase(0, space);
|
||||
}
|
||||
|
||||
caps.dumpToFLog(FLog::Graphics);
|
||||
|
||||
FASTLOG1(FLog::Graphics, "Caps: GL3 %d", caps.ext3);
|
||||
FASTLOG5(FLog::Graphics, "Caps: VAO %d TexStorage %d MapBuffer %d MapBufferRange %d TimerQuery %d", caps.extVertexArrayObject, caps.extTextureStorage, caps.extMapBuffer, caps.extMapBufferRange, caps.extTimerQuery);
|
||||
FASTLOG2(FLog::Graphics, "Caps: DebugMarkers %d Sync %d", caps.extDebugMarkers, caps.extSync);
|
||||
|
||||
immediateContext.reset(new DeviceContextGL(this));
|
||||
|
||||
mainFramebuffer.reset(new FramebufferGL(this, glContext->getMainFramebufferId()));
|
||||
|
||||
std::pair<unsigned int, unsigned int> dimensions = glContext->updateMainFramebuffer(0, 0);
|
||||
|
||||
mainFramebuffer->updateDimensions(dimensions.first, dimensions.second);
|
||||
|
||||
#ifndef GLES
|
||||
if (caps.extTimerQuery)
|
||||
{
|
||||
glGenQueries(1, &frameTimeQueryId);
|
||||
RBXASSERT(frameTimeQueryId);
|
||||
}
|
||||
|
||||
if (FFlag::DebugGraphicsGL && GLEW_ARB_debug_output)
|
||||
{
|
||||
glDebugMessageCallbackARB(debugOutputGLARB, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
Profiler::gpuInit(0);
|
||||
#endif
|
||||
|
||||
if (vr)
|
||||
vr->setup(this);
|
||||
}
|
||||
|
||||
DeviceGL::~DeviceGL()
|
||||
{
|
||||
#ifndef _WIN32
|
||||
Profiler::gpuShutdown();
|
||||
#endif
|
||||
|
||||
vr.reset();
|
||||
immediateContext.reset();
|
||||
mainFramebuffer.reset();
|
||||
|
||||
glContext.reset();
|
||||
}
|
||||
|
||||
std::string DeviceGL::getFeatureLevel()
|
||||
{
|
||||
std::string glString = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||
std::string featureLevel = "OpenGL ";
|
||||
if (!glString.empty())
|
||||
{
|
||||
size_t dotId = glString.find('.');
|
||||
|
||||
// this is based on GLEW implementation of determining GL version. Does it look unsafe? Dont worry, GLEW version is even worse;)
|
||||
if (dotId > 0 && dotId < (glString.length() -1) && isdigit(glString[dotId-1]) && isdigit(glString[dotId+1]))
|
||||
{
|
||||
featureLevel += glString[dotId - 1];
|
||||
featureLevel += '.';
|
||||
featureLevel += glString[dotId + 1];
|
||||
return featureLevel;
|
||||
}
|
||||
}
|
||||
|
||||
featureLevel += "unknown";
|
||||
return featureLevel;
|
||||
}
|
||||
|
||||
bool DeviceGL::validate()
|
||||
{
|
||||
glContext->setCurrent();
|
||||
|
||||
std::pair<unsigned int, unsigned int> dimensions = glContext->updateMainFramebuffer(mainFramebuffer->getWidth(), mainFramebuffer->getHeight());
|
||||
|
||||
mainFramebuffer->updateDimensions(dimensions.first, dimensions.second);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DeviceContext* DeviceGL::beginFrame()
|
||||
{
|
||||
glContext->setCurrent();
|
||||
|
||||
#ifndef GLES
|
||||
if (frameTimeQueryId && !frameTimeQueryIssued)
|
||||
{
|
||||
glBeginQuery(GL_TIME_ELAPSED, frameTimeQueryId);
|
||||
}
|
||||
#endif
|
||||
|
||||
immediateContext->bindFramebuffer(mainFramebuffer.get());
|
||||
immediateContext->clearStates();
|
||||
|
||||
return immediateContext.get();
|
||||
}
|
||||
|
||||
void DeviceGL::endFrame()
|
||||
{
|
||||
#ifndef GLES
|
||||
if (frameTimeQueryId)
|
||||
{
|
||||
if (!frameTimeQueryIssued)
|
||||
{
|
||||
glEndQuery(GL_TIME_ELAPSED);
|
||||
|
||||
frameTimeQueryIssued = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int available = 0;
|
||||
glGetQueryObjectiv(frameTimeQueryId, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
|
||||
if (available)
|
||||
{
|
||||
GLuint64EXT elapsed = 0;
|
||||
if (FFlag::GraphicsGL3)
|
||||
glGetQueryObjectui64v(frameTimeQueryId, GL_QUERY_RESULT, &elapsed);
|
||||
else
|
||||
glGetQueryObjectui64vEXT(frameTimeQueryId, GL_QUERY_RESULT, &elapsed);
|
||||
|
||||
gpuTime = static_cast<float>(elapsed) / 1e6f;
|
||||
|
||||
frameTimeQueryIssued = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (FFlag::GraphicsGLReduceLatency && caps.extSync)
|
||||
{
|
||||
// Wait for last frame to finish on GPU
|
||||
if (frameEventQueryIssued)
|
||||
{
|
||||
int rc = glClientWaitSync(frameEventQueryId, GL_SYNC_FLUSH_COMMANDS_BIT, GLuint64(5e9));
|
||||
RBXASSERT(rc == GL_CONDITION_SATISFIED || rc == GL_ALREADY_SIGNALED);
|
||||
|
||||
glDeleteSync(frameEventQueryId);
|
||||
frameEventQueryId = 0;
|
||||
|
||||
frameEventQueryIssued = false;
|
||||
}
|
||||
|
||||
// Submit new query for frame wait
|
||||
if (!frameEventQueryIssued)
|
||||
{
|
||||
frameEventQueryId = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
RBXASSERT(frameEventQueryId);
|
||||
|
||||
frameEventQueryIssued = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (vr && vrEnabled)
|
||||
{
|
||||
vr->submitFrame(immediateContext.get());
|
||||
}
|
||||
|
||||
glContext->swapBuffers();
|
||||
}
|
||||
|
||||
Framebuffer* DeviceGL::getMainFramebuffer()
|
||||
{
|
||||
return mainFramebuffer.get();
|
||||
}
|
||||
|
||||
DeviceVR* DeviceGL::getVR()
|
||||
{
|
||||
return (vr && vrEnabled) ? vr.get() : NULL;
|
||||
}
|
||||
|
||||
void DeviceGL::setVR(bool enabled)
|
||||
{
|
||||
vrEnabled = enabled;
|
||||
}
|
||||
|
||||
void DeviceGL::defineGlobalConstants(size_t dataSize, const std::vector<ShaderGlobalConstant>& constants)
|
||||
{
|
||||
RBXASSERT(globalConstants.empty());
|
||||
RBXASSERT(!constants.empty());
|
||||
|
||||
globalConstants = constants;
|
||||
|
||||
immediateContext->defineGlobalConstants(dataSize);
|
||||
}
|
||||
|
||||
std::string DeviceGL::getShadingLanguage()
|
||||
{
|
||||
#ifdef GLES
|
||||
return caps.ext3 ? "glsles3" : "glsles";
|
||||
#else
|
||||
return caps.ext3 ? "glsl3" : "glsl";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string DeviceGL::createShaderSource(const std::string& path, const std::string& defines, boost::function<std::string (const std::string&)> fileCallback)
|
||||
{
|
||||
// No preprocessor support
|
||||
return fileCallback(path);
|
||||
}
|
||||
|
||||
std::vector<char> DeviceGL::createShaderBytecode(const std::string& source, const std::string& target, const std::string& entrypoint)
|
||||
{
|
||||
// No bytecode support
|
||||
RBXASSERT(entrypoint == "main");
|
||||
|
||||
return std::vector<char>(source.begin(), source.end());
|
||||
}
|
||||
|
||||
shared_ptr<VertexShader> DeviceGL::createVertexShader(const std::vector<char>& bytecode)
|
||||
{
|
||||
if (!caps.supportsShaders)
|
||||
throw RBX::runtime_error("No shader support");
|
||||
|
||||
std::string source(bytecode.begin(), bytecode.end());
|
||||
|
||||
return shared_ptr<VertexShader>(new VertexShaderGL(this, source));
|
||||
}
|
||||
|
||||
shared_ptr<FragmentShader> DeviceGL::createFragmentShader(const std::vector<char>& bytecode)
|
||||
{
|
||||
if (!caps.supportsShaders)
|
||||
throw RBX::runtime_error("No shader support");
|
||||
|
||||
std::string source(bytecode.begin(), bytecode.end());
|
||||
|
||||
return shared_ptr<FragmentShader>(new FragmentShaderGL(this, source));
|
||||
}
|
||||
|
||||
shared_ptr<ShaderProgram> DeviceGL::createShaderProgram(const shared_ptr<VertexShader>& vertexShader, const shared_ptr<FragmentShader>& fragmentShader)
|
||||
{
|
||||
if (!caps.supportsShaders)
|
||||
throw RBX::runtime_error("No shader support");
|
||||
|
||||
return shared_ptr<ShaderProgram>(new ShaderProgramGL(this, vertexShader, fragmentShader));
|
||||
}
|
||||
|
||||
shared_ptr<ShaderProgram> DeviceGL::createShaderProgramFFP()
|
||||
{
|
||||
throw RBX::runtime_error("No FFP support");
|
||||
}
|
||||
|
||||
shared_ptr<VertexBuffer> DeviceGL::createVertexBuffer(size_t elementSize, size_t elementCount, VertexBuffer::Usage usage)
|
||||
{
|
||||
return shared_ptr<VertexBuffer>(new VertexBufferGL(this, elementSize, elementCount, usage));
|
||||
}
|
||||
|
||||
shared_ptr<IndexBuffer> DeviceGL::createIndexBuffer(size_t elementSize, size_t elementCount, VertexBuffer::Usage usage)
|
||||
{
|
||||
return shared_ptr<IndexBuffer>(new IndexBufferGL(this, elementSize, elementCount, usage));
|
||||
}
|
||||
|
||||
shared_ptr<VertexLayout> DeviceGL::createVertexLayout(const std::vector<VertexLayout::Element>& elements)
|
||||
{
|
||||
return shared_ptr<VertexLayout>(new VertexLayoutGL(this, elements));
|
||||
}
|
||||
|
||||
shared_ptr<Texture> DeviceGL::createTexture(Texture::Type type, Texture::Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Texture::Usage usage)
|
||||
{
|
||||
return shared_ptr<Texture>(new TextureGL(this, type, format, width, height, depth, mipLevels, usage));
|
||||
}
|
||||
|
||||
shared_ptr<Renderbuffer> DeviceGL::createRenderbuffer(Texture::Format format, unsigned int width, unsigned int height, unsigned int samples)
|
||||
{
|
||||
return shared_ptr<Renderbuffer>(new RenderbufferGL(this, format, width, height, samples));
|
||||
}
|
||||
|
||||
shared_ptr<Geometry> DeviceGL::createGeometryImpl(const shared_ptr<VertexLayout>& layout, const std::vector<shared_ptr<VertexBuffer> >& vertexBuffers, const shared_ptr<IndexBuffer>& indexBuffer, unsigned int baseVertexIndex)
|
||||
{
|
||||
return shared_ptr<Geometry>(new GeometryGL(this, layout, vertexBuffers, indexBuffer, baseVertexIndex));
|
||||
}
|
||||
|
||||
shared_ptr<Framebuffer> DeviceGL::createFramebufferImpl(const std::vector<shared_ptr<Renderbuffer> >& color, const shared_ptr<Renderbuffer>& depth)
|
||||
{
|
||||
return shared_ptr<Framebuffer>(new FramebufferGL(this, color, depth));
|
||||
}
|
||||
|
||||
DeviceStats DeviceGL::getStatistics() const
|
||||
{
|
||||
DeviceStats result = {};
|
||||
|
||||
result.gpuFrameTime = gpuTime;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include "GfxCore/Device.h"
|
||||
#include "GfxCore/States.h"
|
||||
|
||||
typedef struct __GLsync *GLsync;
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class FramebufferGL;
|
||||
class ShaderProgramGL;
|
||||
class TextureGL;
|
||||
class ContextGL;
|
||||
class DeviceGL;
|
||||
|
||||
class DeviceVRGL: public DeviceVR
|
||||
{
|
||||
public:
|
||||
virtual void setup(Device* device) = 0;
|
||||
virtual void submitFrame(DeviceContext* context) = 0;
|
||||
|
||||
static DeviceVRGL* createCardboard();
|
||||
};
|
||||
|
||||
struct DeviceCapsGL: DeviceCaps
|
||||
{
|
||||
bool ext3;
|
||||
|
||||
bool extVertexArrayObject;
|
||||
bool extTextureStorage;
|
||||
bool extMapBuffer;
|
||||
bool extMapBufferRange;
|
||||
bool extTimerQuery;
|
||||
bool extDebugMarkers;
|
||||
bool extSync;
|
||||
};
|
||||
|
||||
class DeviceContextGL: public DeviceContext
|
||||
{
|
||||
public:
|
||||
DeviceContextGL(DeviceGL* dev);
|
||||
~DeviceContextGL();
|
||||
|
||||
void defineGlobalConstants(size_t dataSize);
|
||||
|
||||
void clearStates();
|
||||
|
||||
void invalidateCachedProgram();
|
||||
void invalidateCachedTexture(Texture* texture);
|
||||
void invalidateCachedTextureStage(unsigned int stage);
|
||||
|
||||
virtual void setDefaultAnisotropy(unsigned int value);
|
||||
|
||||
virtual void updateGlobalConstants(const void* data, size_t dataSize);
|
||||
|
||||
virtual void bindFramebuffer(Framebuffer* buffer);
|
||||
virtual void clearFramebuffer(unsigned int mask, const float color[4], float depth, unsigned int stencil);
|
||||
|
||||
virtual void copyFramebuffer(Framebuffer* buffer, Texture* texture);
|
||||
virtual void resolveFramebuffer(Framebuffer* msaaBuffer, Framebuffer* buffer, unsigned int mask);
|
||||
virtual void discardFramebuffer(Framebuffer* buffer, unsigned int mask);
|
||||
|
||||
virtual void bindProgram(ShaderProgram* program);
|
||||
virtual void setWorldTransforms4x3(const float* data, size_t matrixCount);
|
||||
virtual void setConstant(int handle, const float* data, size_t vectorCount);
|
||||
|
||||
virtual void bindTexture(unsigned int stage, Texture* texture, const SamplerState& state);
|
||||
|
||||
virtual void setRasterizerState(const RasterizerState& state);
|
||||
virtual void setBlendState(const BlendState& state);
|
||||
virtual void setDepthState(const DepthState& state);
|
||||
|
||||
virtual void drawImpl(Geometry* geometry, Geometry::Primitive primitive, unsigned int offset, unsigned int count, unsigned int indexRangeBegin, unsigned int indexRangeEnd);
|
||||
|
||||
virtual void pushDebugMarkerGroup(const char* text);
|
||||
virtual void popDebugMarkerGroup();
|
||||
virtual void setDebugMarker(const char* text);
|
||||
|
||||
private:
|
||||
std::vector<char> globalData;
|
||||
unsigned int globalDataVersion;
|
||||
|
||||
unsigned int defaultAnisotropy;
|
||||
|
||||
ShaderProgramGL* cachedProgram;
|
||||
TextureGL* cachedTextures[16];
|
||||
|
||||
RasterizerState cachedRasterizerState;
|
||||
BlendState cachedBlendState;
|
||||
DepthState cachedDepthState;
|
||||
DeviceGL* device;
|
||||
};
|
||||
|
||||
class DeviceGL: public Device
|
||||
{
|
||||
public:
|
||||
DeviceGL(void* windowHandle);
|
||||
~DeviceGL();
|
||||
|
||||
virtual bool validate();
|
||||
|
||||
virtual DeviceContext* beginFrame();
|
||||
virtual void endFrame();
|
||||
|
||||
virtual Framebuffer* getMainFramebuffer();
|
||||
|
||||
virtual DeviceVR* getVR();
|
||||
virtual void setVR(bool enabled);
|
||||
|
||||
virtual void defineGlobalConstants(size_t dataSize, const std::vector<ShaderGlobalConstant>& constants);
|
||||
|
||||
virtual std::string getAPIName() { return "OpenGL"; }
|
||||
virtual std::string getFeatureLevel();
|
||||
virtual std::string getShadingLanguage();
|
||||
virtual std::string createShaderSource(const std::string& path, const std::string& defines, boost::function<std::string (const std::string&)> fileCallback);
|
||||
virtual std::vector<char> createShaderBytecode(const std::string& source, const std::string& target, const std::string& entrypoint);
|
||||
|
||||
virtual shared_ptr<VertexShader> createVertexShader(const std::vector<char>& bytecode);
|
||||
virtual shared_ptr<FragmentShader> createFragmentShader(const std::vector<char>& bytecode);
|
||||
virtual shared_ptr<ShaderProgram> createShaderProgram(const shared_ptr<VertexShader>& vertexShader, const shared_ptr<FragmentShader>& fragmentShader);
|
||||
virtual shared_ptr<ShaderProgram> createShaderProgramFFP();
|
||||
|
||||
virtual shared_ptr<VertexBuffer> createVertexBuffer(size_t elementSize, size_t elementCount, GeometryBuffer::Usage usage);
|
||||
virtual shared_ptr<IndexBuffer> createIndexBuffer(size_t elementSize, size_t elementCount, GeometryBuffer::Usage usage);
|
||||
virtual shared_ptr<VertexLayout> createVertexLayout(const std::vector<VertexLayout::Element>& elements);
|
||||
|
||||
virtual shared_ptr<Texture> createTexture(Texture::Type type, Texture::Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Texture::Usage usage);
|
||||
|
||||
virtual shared_ptr<Renderbuffer> createRenderbuffer(Texture::Format format, unsigned int width, unsigned int height, unsigned int samples);
|
||||
|
||||
virtual shared_ptr<Geometry> createGeometryImpl(const shared_ptr<VertexLayout>& layout, const std::vector<shared_ptr<VertexBuffer> >& vertexBuffers, const shared_ptr<IndexBuffer>& indexBuffer, unsigned int baseVertexIndex);
|
||||
|
||||
virtual shared_ptr<Framebuffer> createFramebufferImpl(const std::vector<shared_ptr<Renderbuffer> >& color, const shared_ptr<Renderbuffer>& depth);
|
||||
|
||||
virtual const DeviceCaps& getCaps() const { return caps; }
|
||||
|
||||
virtual DeviceStats getStatistics() const;
|
||||
|
||||
DeviceContextGL* getImmediateContextGL() { return immediateContext.get(); }
|
||||
|
||||
const DeviceCapsGL& getCapsGL() const { return caps; }
|
||||
|
||||
const std::vector<ShaderGlobalConstant>& getGlobalConstants() const { return globalConstants; }
|
||||
|
||||
private:
|
||||
DeviceCapsGL caps;
|
||||
|
||||
scoped_ptr<DeviceContextGL> immediateContext;
|
||||
|
||||
scoped_ptr<FramebufferGL> mainFramebuffer;
|
||||
|
||||
std::vector<ShaderGlobalConstant> globalConstants;
|
||||
|
||||
unsigned int frameTimeQueryId;
|
||||
bool frameTimeQueryIssued;
|
||||
|
||||
GLsync frameEventQueryId;
|
||||
bool frameEventQueryIssued;
|
||||
|
||||
float gpuTime;
|
||||
|
||||
scoped_ptr<DeviceVRGL> vr;
|
||||
bool vrEnabled;
|
||||
|
||||
scoped_ptr<ContextGL> glContext;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
#include "FramebufferGL.h"
|
||||
|
||||
#include "TextureGL.h"
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
RenderbufferGL::RenderbufferGL(Device* device, const shared_ptr<TextureGL>& container, unsigned int target)
|
||||
: Renderbuffer(device, container->getFormat(), container->getWidth(), container->getHeight(), 1)
|
||||
, owner(container)
|
||||
, target(target)
|
||||
, bufferId(0)
|
||||
{
|
||||
}
|
||||
|
||||
RenderbufferGL::RenderbufferGL(Device* device, Texture::Format format, unsigned int width, unsigned int height, unsigned int samples)
|
||||
: Renderbuffer(device, format, width, height, samples)
|
||||
, target(-1)
|
||||
, bufferId(0)
|
||||
{
|
||||
RBXASSERT(samples);
|
||||
if (samples > device->getCaps().maxSamples)
|
||||
throw RBX::runtime_error("Unsupported renderbuffer: too many samples (%d)", samples);
|
||||
|
||||
glGenRenderbuffers(1, &bufferId);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, bufferId);
|
||||
|
||||
if (samples > 1)
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, TextureGL::getInternalFormat(format), width, height);
|
||||
else
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, TextureGL::getInternalFormat(format), width, height);
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
}
|
||||
|
||||
RenderbufferGL::~RenderbufferGL()
|
||||
{
|
||||
if (bufferId)
|
||||
glDeleteRenderbuffers(1, &bufferId);
|
||||
}
|
||||
|
||||
FramebufferGL::FramebufferGL(Device* device, unsigned int id)
|
||||
: Framebuffer(device, 0, 0, 1)
|
||||
, id(id)
|
||||
{
|
||||
}
|
||||
|
||||
FramebufferGL::FramebufferGL(Device* device, const std::vector<shared_ptr<Renderbuffer> >& color, const shared_ptr<Renderbuffer>& depth)
|
||||
: Framebuffer(device, 0, 0, 0)
|
||||
, id(0)
|
||||
, color(color)
|
||||
, depth(depth)
|
||||
{
|
||||
RBXASSERT(!color.empty());
|
||||
|
||||
if (color.size() > device->getCaps().maxDrawBuffers)
|
||||
throw RBX::runtime_error("Unsupported framebuffer configuration: too many buffers (%d)", (int)color.size());
|
||||
|
||||
glGenFramebuffers(1, &id);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, id);
|
||||
|
||||
for (size_t i = 0; i < color.size(); ++i)
|
||||
{
|
||||
RenderbufferGL* buffer = static_cast<RenderbufferGL*>(color[i].get());
|
||||
RBXASSERT(buffer);
|
||||
RBXASSERT(!Texture::isFormatDepth(buffer->getFormat()));
|
||||
|
||||
if (buffer->getTextureId())
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, buffer->getTarget(), buffer->getTextureId(), 0);
|
||||
else
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, buffer->getBufferId());
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
width = buffer->getWidth();
|
||||
height = buffer->getHeight();
|
||||
samples = buffer->getSamples();
|
||||
}
|
||||
else
|
||||
{
|
||||
RBXASSERT(width == buffer->getWidth());
|
||||
RBXASSERT(height == buffer->getHeight());
|
||||
RBXASSERT(samples == buffer->getSamples());
|
||||
}
|
||||
}
|
||||
|
||||
if (depth)
|
||||
{
|
||||
RenderbufferGL* buffer = static_cast<RenderbufferGL*>(depth.get());
|
||||
RBXASSERT(buffer->getTextureId() == 0);
|
||||
RBXASSERT(Texture::isFormatDepth(buffer->getFormat()));
|
||||
|
||||
RBXASSERT(width == buffer->getWidth());
|
||||
RBXASSERT(height == buffer->getHeight());
|
||||
RBXASSERT(samples == buffer->getSamples());
|
||||
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer->getBufferId());
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer->getBufferId());
|
||||
}
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
glDeleteFramebuffers(1, &id);
|
||||
|
||||
throw RBX::runtime_error("Unsupported framebuffer configuration: error %x", status);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
FramebufferGL::~FramebufferGL()
|
||||
{
|
||||
if (id)
|
||||
glDeleteFramebuffers(1, &id);
|
||||
}
|
||||
|
||||
void FramebufferGL::download(void* data, unsigned int size)
|
||||
{
|
||||
RBXASSERT(size == width * height * 4);
|
||||
|
||||
GLint oldfb = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldfb);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, id);
|
||||
|
||||
#ifndef GLES
|
||||
glReadBuffer(id == 0 ? GL_BACK : GL_COLOR_ATTACHMENT0);
|
||||
#endif
|
||||
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldfb);
|
||||
|
||||
std::vector<char> tempRow(width * 4);
|
||||
|
||||
for (unsigned int y = 0; y < height / 2; ++y)
|
||||
{
|
||||
char* dataRow = static_cast<char*>(data) + y * width * 4;
|
||||
char* dataOppositeRow = static_cast<char*>(data) + (height - 1 - y) * width * 4;
|
||||
|
||||
memcpy(&tempRow[0], dataRow, width * 4);
|
||||
memcpy(dataRow, dataOppositeRow, width * 4);
|
||||
memcpy(dataOppositeRow, &tempRow[0], width * 4);
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferGL::updateDimensions(unsigned int width, unsigned int height)
|
||||
{
|
||||
RBXASSERT(color.empty() && !depth);
|
||||
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "GfxCore/Framebuffer.h"
|
||||
#include "TextureGL.h"
|
||||
#include <vector>
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class RenderbufferGL: public Renderbuffer
|
||||
{
|
||||
public:
|
||||
RenderbufferGL(Device* device, const shared_ptr<TextureGL>& owner, unsigned int target);
|
||||
RenderbufferGL(Device* device, Texture::Format format, unsigned int width, unsigned int height, unsigned int samples);
|
||||
~RenderbufferGL();
|
||||
|
||||
unsigned int getTextureId() const { return owner ? owner->getId() : 0; }
|
||||
unsigned int getTarget() const { return target; }
|
||||
unsigned int getBufferId() const { return bufferId; }
|
||||
|
||||
private:
|
||||
unsigned int target; // TEXTURE_2D or TEXTURE_CUBE_MAP_POSITIVE_X, ...
|
||||
unsigned int bufferId;
|
||||
shared_ptr<TextureGL> owner;
|
||||
};
|
||||
|
||||
class FramebufferGL: public Framebuffer
|
||||
{
|
||||
public:
|
||||
FramebufferGL(Device* device, unsigned int id);
|
||||
FramebufferGL(Device* device, const std::vector<shared_ptr<Renderbuffer> >& color, const shared_ptr<Renderbuffer>& depth);
|
||||
~FramebufferGL();
|
||||
|
||||
virtual void download(void* data, unsigned int size);
|
||||
|
||||
void updateDimensions(unsigned int width, unsigned int height);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
|
||||
unsigned int getDrawBuffers() const { return color.size(); }
|
||||
|
||||
private:
|
||||
unsigned int id;
|
||||
|
||||
std::vector<shared_ptr<Renderbuffer> > color;
|
||||
shared_ptr<Renderbuffer> depth;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
#if defined(__ANDROID__)
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "FramebufferGL.h"
|
||||
#include "TextureGL.h"
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include "VrApi.h"
|
||||
#include "VrApi_Helpers.h"
|
||||
|
||||
LOGGROUP(VR)
|
||||
|
||||
FASTFLAGVARIABLE(GearVR, false)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
static ovrJava gJava;
|
||||
|
||||
static DeviceVR::Pose getPose(const ovrPosef& pose, unsigned int statusFlags)
|
||||
{
|
||||
DeviceVR::Pose result = {};
|
||||
|
||||
result.valid = (statusFlags & VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED) != 0;
|
||||
|
||||
result.position[0] = pose.Position.x;
|
||||
result.position[1] = pose.Position.y;
|
||||
result.position[2] = pose.Position.z;
|
||||
|
||||
result.orientation[0] = pose.Orientation.x;
|
||||
result.orientation[1] = pose.Orientation.y;
|
||||
result.orientation[2] = pose.Orientation.z;
|
||||
result.orientation[3] = pose.Orientation.w;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct VRTexture
|
||||
{
|
||||
static const int kMaxCount = 4;
|
||||
|
||||
shared_ptr<Framebuffer> fb[kMaxCount];
|
||||
|
||||
ovrTextureSwapChain* swapChain;
|
||||
unsigned int swapChainIndex;
|
||||
|
||||
VRTexture(): swapChain(NULL), swapChainIndex(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct GearVRGL: DeviceVRGL
|
||||
{
|
||||
ovrMobile* session;
|
||||
float eyeFovX, eyeFovY;
|
||||
VRTexture textures[2];
|
||||
|
||||
long long frameIndex;
|
||||
double sensorSampleTime;
|
||||
ovrTracking trackingState;
|
||||
|
||||
GearVRGL(): session(NULL), frameIndex(0), sensorSampleTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
~GearVRGL()
|
||||
{
|
||||
vrapi_LeaveVrMode(session);
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
vrapi_DestroyTextureSwapChain(textures[eye].swapChain);
|
||||
|
||||
vrapi_Shutdown();
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
frameIndex++;
|
||||
|
||||
double frameTime = vrapi_GetPredictedDisplayTime(session, frameIndex);
|
||||
|
||||
sensorSampleTime = vrapi_GetTimeInSeconds();
|
||||
|
||||
ovrTracking baseState = vrapi_GetPredictedTracking(session, frameTime);
|
||||
|
||||
ovrHeadModelParms headModel = vrapi_DefaultHeadModelParms();
|
||||
|
||||
trackingState = vrapi_ApplyHeadModel(&headModel, &baseState);
|
||||
}
|
||||
|
||||
void recenter() override
|
||||
{
|
||||
vrapi_RecenterPose(session);
|
||||
}
|
||||
|
||||
Framebuffer* getEyeFramebuffer(int eye) override
|
||||
{
|
||||
RBXASSERT(eye == 0 || eye == 1);
|
||||
|
||||
return textures[eye].fb[textures[eye].swapChainIndex].get();
|
||||
}
|
||||
|
||||
State getState() override
|
||||
{
|
||||
State result = {};
|
||||
|
||||
result.headPose = getPose(trackingState.HeadPose.Pose, trackingState.Status);
|
||||
|
||||
ovrHeadModelParms headModel = vrapi_DefaultHeadModelParms();
|
||||
|
||||
float tanX = tanf(eyeFovX / 2 * (3.1415926f / 180));
|
||||
float tanY = tanf(eyeFovY / 2 * (3.1415926f / 180));
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
float eyeSign = (eye == 0 ? -1 : 1);
|
||||
|
||||
result.eyeOffset[eye][0] = eyeSign * headModel.InterpupillaryDistance / 2;
|
||||
result.eyeOffset[eye][1] = 0;
|
||||
result.eyeOffset[eye][2] = 0;
|
||||
|
||||
result.eyeFov[eye][0] = tanY;
|
||||
result.eyeFov[eye][1] = tanY;
|
||||
result.eyeFov[eye][2] = tanX;
|
||||
result.eyeFov[eye][3] = tanX;
|
||||
}
|
||||
|
||||
result.needsMirror = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void submitFrame(DeviceContext* context) override
|
||||
{
|
||||
double currentTime = vrapi_GetTimeInSeconds();
|
||||
|
||||
ovrMatrix4f eyeProjectionMatrix = ovrMatrix4f_CreateProjectionFov(eyeFovX, eyeFovY, 0, 0, VRAPI_ZNEAR, 0);
|
||||
ovrMatrix4f tanAngleMatrix = ovrMatrix4f_TanAngleMatrixFromProjection(&eyeProjectionMatrix);
|
||||
|
||||
ovrFrameParms params = vrapi_DefaultFrameParms(&gJava, VRAPI_FRAME_INIT_DEFAULT, currentTime, NULL);
|
||||
params.FrameIndex = frameIndex;
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
params.Layers[VRAPI_FRAME_LAYER_TYPE_WORLD].Textures[eye].ColorTextureSwapChain = textures[eye].swapChain;
|
||||
params.Layers[VRAPI_FRAME_LAYER_TYPE_WORLD].Textures[eye].TextureSwapChainIndex = textures[eye].swapChainIndex;
|
||||
params.Layers[VRAPI_FRAME_LAYER_TYPE_WORLD].Textures[eye].TexCoordsFromTanAngles = tanAngleMatrix;
|
||||
params.Layers[VRAPI_FRAME_LAYER_TYPE_WORLD].Textures[eye].HeadPose = trackingState.HeadPose;
|
||||
}
|
||||
|
||||
vrapi_SubmitFrame(session, ¶ms);
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
textures[eye].swapChainIndex++;
|
||||
textures[eye].swapChainIndex %= vrapi_GetTextureSwapChainLength(textures[eye].swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
void setup(Device* device) override
|
||||
{
|
||||
unsigned int width = vrapi_GetSystemPropertyInt(&gJava, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH);
|
||||
unsigned int height = vrapi_GetSystemPropertyInt(&gJava, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT);
|
||||
|
||||
shared_ptr<Renderbuffer> depthStencil = device->createRenderbuffer(Texture::Format_D24S8, width, height, 1);
|
||||
|
||||
for (int eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
textures[eye].swapChain = vrapi_CreateTextureSwapChain(VRAPI_TEXTURE_TYPE_2D, VRAPI_TEXTURE_FORMAT_8888, width, height, 1, true);
|
||||
|
||||
int textureCount = vrapi_GetTextureSwapChainLength(textures[eye].swapChain);
|
||||
|
||||
RBXASSERT(textureCount <= VRTexture::kMaxCount);
|
||||
|
||||
for (int i = 0; i < textureCount; ++i)
|
||||
{
|
||||
unsigned int textureId = vrapi_GetTextureSwapChainHandle(textures[eye].swapChain, i);
|
||||
|
||||
shared_ptr<Texture> texture(new TextureGL(device, Texture::Type_2D, Texture::Format_RGBA8, width, height, 1, 1, Texture::Usage_Renderbuffer, textureId));
|
||||
|
||||
textures[eye].fb[i] = device->createFramebuffer(texture->getRenderbuffer(0, 0), depthStencil);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DeviceVRGL* DeviceVRGL::createGear()
|
||||
{
|
||||
if (!FFlag::GearVR)
|
||||
return NULL;
|
||||
|
||||
ovrInitParms initParms = vrapi_DefaultInitParms(&gJava);
|
||||
|
||||
ovrInitializeStatus status = vrapi_Initialize(&initParms);
|
||||
|
||||
if (status != VRAPI_INITIALIZE_SUCCESS)
|
||||
{
|
||||
FASTLOG1(FLog::VR, "VR: vrapi_Initialize returned %d", status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ovrModeParms modeParms = vrapi_DefaultModeParms(&gJava);
|
||||
|
||||
ovrMobile* session = vrapi_EnterVrMode(&modeParms);
|
||||
|
||||
GearVRGL* vr = new GearVRGL();
|
||||
|
||||
vr->session = session;
|
||||
|
||||
vr->eyeFovX = vrapi_GetSystemPropertyFloat(&gJava, VRAPI_SYS_PROP_SUGGESTED_EYE_FOV_DEGREES_X);
|
||||
vr->eyeFovY = vrapi_GetSystemPropertyFloat(&gJava, VRAPI_SYS_PROP_SUGGESTED_EYE_FOV_DEGREES_Y);
|
||||
|
||||
return vr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void vrGearSetJava(JavaVM* vm, JNIEnv* env, jobject activity)
|
||||
{
|
||||
using RBX::Graphics::gJava;
|
||||
|
||||
gJava.Vm = vm;
|
||||
gJava.Env = env;
|
||||
gJava.ActivityObject = activity;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,345 @@
|
||||
#include "GeometryGL.h"
|
||||
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
static const GLenum gBufferUsageGL[GeometryBuffer::Usage_Count] =
|
||||
{
|
||||
GL_STATIC_DRAW,
|
||||
GL_DYNAMIC_DRAW
|
||||
};
|
||||
|
||||
static const GLenum gBufferLockGL[GeometryBuffer::Lock_Count] =
|
||||
{
|
||||
GL_MAP_WRITE_BIT,
|
||||
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT
|
||||
};
|
||||
|
||||
struct VertexFormatGL
|
||||
{
|
||||
int size;
|
||||
GLenum type;
|
||||
bool normalized;
|
||||
};
|
||||
|
||||
static const VertexFormatGL gVertexFormatGL[VertexLayout::Format_Count] =
|
||||
{
|
||||
{ 1, GL_FLOAT, false },
|
||||
{ 2, GL_FLOAT, false },
|
||||
{ 3, GL_FLOAT, false },
|
||||
{ 4, GL_FLOAT, false },
|
||||
{ 2, GL_SHORT, false },
|
||||
{ 4, GL_SHORT, false },
|
||||
{ 4, GL_UNSIGNED_BYTE, false },
|
||||
{ 4, GL_UNSIGNED_BYTE, true },
|
||||
};
|
||||
|
||||
static const GLenum gGeometryPrimitiveGL[Geometry::Primitive_Count] =
|
||||
{
|
||||
GL_TRIANGLES,
|
||||
GL_LINES,
|
||||
GL_POINTS,
|
||||
GL_TRIANGLE_STRIP
|
||||
};
|
||||
|
||||
static unsigned int getVertexAttributeGL(VertexLayout::Semantic semantic, unsigned int index)
|
||||
{
|
||||
switch (semantic)
|
||||
{
|
||||
case VertexLayout::Semantic_Position:
|
||||
RBXASSERT(index == 0);
|
||||
return 0;
|
||||
|
||||
case VertexLayout::Semantic_Normal:
|
||||
RBXASSERT(index == 0);
|
||||
return 1;
|
||||
|
||||
case VertexLayout::Semantic_Color:
|
||||
RBXASSERT(index < 2);
|
||||
return 2 + index;
|
||||
|
||||
case VertexLayout::Semantic_Texture:
|
||||
RBXASSERT(index < 8);
|
||||
return 4 + index;
|
||||
|
||||
default:
|
||||
RBXASSERT(false);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
const char* VertexLayoutGL::getShaderAttributeName(unsigned int id)
|
||||
{
|
||||
static const char* table[] =
|
||||
{
|
||||
"vertex",
|
||||
"normal",
|
||||
"colour",
|
||||
"secondary_colour",
|
||||
"uv0",
|
||||
"uv1",
|
||||
"uv2",
|
||||
"uv3",
|
||||
"uv4",
|
||||
"uv5",
|
||||
"uv6",
|
||||
"uv7",
|
||||
};
|
||||
|
||||
return (id < ARRAYSIZE(table)) ? table[id] : NULL;
|
||||
}
|
||||
|
||||
VertexLayoutGL::VertexLayoutGL(Device* device, const std::vector<Element>& elements)
|
||||
: VertexLayout(device, elements)
|
||||
{
|
||||
}
|
||||
|
||||
VertexLayoutGL::~VertexLayoutGL()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Base> GeometryBufferGL<Base>::GeometryBufferGL(Device* device, size_t elementSize, size_t elementCount, GeometryBuffer::Usage usage, unsigned int target)
|
||||
: Base(device, elementSize, elementCount, usage)
|
||||
, target(target)
|
||||
, id(0)
|
||||
, locked(0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Base> void GeometryBufferGL<Base>::create()
|
||||
{
|
||||
glGenBuffers(1, &id);
|
||||
RBXASSERT(id);
|
||||
|
||||
glBindBuffer(target, id);
|
||||
glBufferData(target, this->elementSize * this->elementCount, NULL, gBufferUsageGL[this->usage]);
|
||||
}
|
||||
|
||||
template <typename Base> GeometryBufferGL<Base>::~GeometryBufferGL()
|
||||
{
|
||||
RBXASSERT(!locked);
|
||||
|
||||
glDeleteBuffers(1, &id);
|
||||
}
|
||||
|
||||
template <typename Base> void* GeometryBufferGL<Base>::lock(GeometryBuffer::LockMode mode)
|
||||
{
|
||||
RBXASSERT(!locked);
|
||||
|
||||
unsigned int size = this->elementSize * this->elementCount;
|
||||
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(this->device)->getCapsGL();
|
||||
|
||||
if (caps.extMapBuffer)
|
||||
{
|
||||
glBindBuffer(target, id);
|
||||
|
||||
if (caps.extMapBufferRange)
|
||||
{
|
||||
locked = glMapBufferRange(target, 0, size, gBufferLockGL[mode]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mode == GeometryBuffer::Lock_Discard)
|
||||
glBufferData(target, size, NULL, gBufferUsageGL[this->usage]);
|
||||
|
||||
locked = glMapBuffer(target, GL_WRITE_ONLY);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
locked = new char[size];
|
||||
}
|
||||
|
||||
RBXASSERT(locked);
|
||||
|
||||
return locked;
|
||||
}
|
||||
|
||||
template <typename Base> void GeometryBufferGL<Base>::unlock()
|
||||
{
|
||||
RBXASSERT(locked);
|
||||
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(this->device)->getCapsGL();
|
||||
|
||||
if (caps.extMapBuffer)
|
||||
{
|
||||
glBindBuffer(target, id);
|
||||
glUnmapBuffer(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindBuffer(target, id);
|
||||
glBufferSubData(target, 0, this->elementSize * this->elementCount, locked);
|
||||
|
||||
delete[] static_cast<char*>(locked);
|
||||
}
|
||||
|
||||
locked = NULL;
|
||||
}
|
||||
|
||||
template <typename Base> void GeometryBufferGL<Base>::upload(unsigned int offset, const void* data, unsigned int size)
|
||||
{
|
||||
RBXASSERT(!locked);
|
||||
RBXASSERT(offset + size <= this->elementSize * this->elementCount);
|
||||
|
||||
glBindBuffer(target, id);
|
||||
glBufferSubData(target, offset, size, data);
|
||||
}
|
||||
|
||||
VertexBufferGL::VertexBufferGL(Device* device, size_t elementSize, size_t elementCount, Usage usage)
|
||||
: GeometryBufferGL<VertexBuffer>(device, elementSize, elementCount, usage, GL_ARRAY_BUFFER)
|
||||
{
|
||||
create();
|
||||
}
|
||||
|
||||
VertexBufferGL::~VertexBufferGL()
|
||||
{
|
||||
}
|
||||
|
||||
IndexBufferGL::IndexBufferGL(Device* device, size_t elementSize, size_t elementCount, Usage usage)
|
||||
: GeometryBufferGL<IndexBuffer>(device, elementSize, elementCount, usage, GL_ELEMENT_ARRAY_BUFFER)
|
||||
{
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(device)->getCapsGL();
|
||||
|
||||
if (elementSize != 2 && elementSize != 4)
|
||||
throw RBX::runtime_error("Invalid element size: %d", (int)elementSize);
|
||||
|
||||
if (elementSize == 4 && !caps.supportsIndex32)
|
||||
throw RBX::runtime_error("Unsupported element size: %d", (int)elementSize);
|
||||
|
||||
create();
|
||||
}
|
||||
|
||||
IndexBufferGL::~IndexBufferGL()
|
||||
{
|
||||
}
|
||||
|
||||
GeometryGL::GeometryGL(Device* device, const shared_ptr<VertexLayout>& layout, const std::vector<shared_ptr<VertexBuffer> >& vertexBuffers, const shared_ptr<IndexBuffer>& indexBuffer, unsigned int baseVertexIndex)
|
||||
: Geometry(device, layout, vertexBuffers, indexBuffer, baseVertexIndex)
|
||||
, id(0)
|
||||
, indexElementSize(0)
|
||||
{
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(device)->getCapsGL();
|
||||
|
||||
if (caps.extVertexArrayObject)
|
||||
{
|
||||
glGenVertexArrays(1, &id);
|
||||
RBXASSERT(id);
|
||||
|
||||
glBindVertexArray(id);
|
||||
|
||||
bindArrays();
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
if (indexBuffer)
|
||||
{
|
||||
// Cache indexElementSize to reduce cache misses in case we're using VAO
|
||||
indexElementSize = indexBuffer->getElementSize();
|
||||
}
|
||||
}
|
||||
|
||||
GeometryGL::~GeometryGL()
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
glDeleteVertexArrays(1, &id);
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryGL::draw(Primitive primitive, unsigned int offset, unsigned int count)
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
|
||||
// Setup vertex/index buffers
|
||||
if (id)
|
||||
glBindVertexArray(id);
|
||||
else
|
||||
mask = bindArrays();
|
||||
|
||||
// Perform draw call
|
||||
if (indexBuffer)
|
||||
{
|
||||
GLenum indexElementType = indexElementSize == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
|
||||
|
||||
glDrawElements(gGeometryPrimitiveGL[primitive], count, indexElementType, reinterpret_cast<void*>(offset * indexElementSize));
|
||||
}
|
||||
else
|
||||
glDrawArrays(gGeometryPrimitiveGL[primitive], offset, count);
|
||||
|
||||
// Unbind buffers to prevent draw call interference
|
||||
if (id)
|
||||
glBindVertexArray(0);
|
||||
else
|
||||
unbindArrays(mask);
|
||||
}
|
||||
|
||||
unsigned int GeometryGL::bindArrays()
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
unsigned int vbId = 0;
|
||||
|
||||
const std::vector<VertexLayout::Element>& elements = layout->getElements();
|
||||
|
||||
for (size_t i = 0; i < elements.size(); ++i)
|
||||
{
|
||||
const VertexLayout::Element& e = elements[i];
|
||||
|
||||
int index = getVertexAttributeGL(e.semantic, e.semanticIndex);
|
||||
RBXASSERT(index >= 0);
|
||||
|
||||
RBXASSERT(e.stream < vertexBuffers.size());
|
||||
|
||||
VertexBufferGL* vb = static_cast<VertexBufferGL*>(vertexBuffers[e.stream].get());
|
||||
|
||||
RBXASSERT(e.offset < vb->getElementSize());
|
||||
|
||||
size_t stride = vb->getElementSize();
|
||||
size_t offset = e.offset + stride * baseVertexIndex;
|
||||
|
||||
const VertexFormatGL& formatGl = gVertexFormatGL[e.format];
|
||||
|
||||
if (vbId != vb->getId())
|
||||
{
|
||||
vbId = vb->getId();
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbId);
|
||||
}
|
||||
|
||||
glEnableVertexAttribArray(index);
|
||||
glVertexAttribPointer(index, formatGl.size, formatGl.type, formatGl.normalized, stride, reinterpret_cast<void*>(offset));
|
||||
|
||||
mask |= 1 << index;
|
||||
}
|
||||
|
||||
if (indexBuffer)
|
||||
{
|
||||
IndexBufferGL* ib = static_cast<IndexBufferGL*>(indexBuffer.get());
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->getId());
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
void GeometryGL::unbindArrays(unsigned int mask)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
if (mask & (1 << i))
|
||||
glDisableVertexAttribArray(i);
|
||||
}
|
||||
|
||||
// Instantiate GeometryBufferGL template
|
||||
template class GeometryBufferGL<VertexBuffer>;
|
||||
template class GeometryBufferGL<IndexBuffer>;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "GfxCore/Geometry.h"
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class VertexLayoutGL: public VertexLayout
|
||||
{
|
||||
public:
|
||||
static const char* getShaderAttributeName(unsigned int id);
|
||||
|
||||
VertexLayoutGL(Device* device, const std::vector<Element>& elements);
|
||||
~VertexLayoutGL();
|
||||
};
|
||||
|
||||
template <typename Base> class GeometryBufferGL: public Base
|
||||
{
|
||||
public:
|
||||
GeometryBufferGL(Device* device, size_t elementSize, size_t elementCount, GeometryBuffer::Usage usage, unsigned int target);
|
||||
~GeometryBufferGL();
|
||||
|
||||
virtual void* lock(GeometryBuffer::LockMode mode);
|
||||
virtual void unlock();
|
||||
|
||||
virtual void upload(unsigned int offset, const void* data, unsigned int size);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
|
||||
protected:
|
||||
void create();
|
||||
|
||||
private:
|
||||
unsigned int target;
|
||||
unsigned int id;
|
||||
|
||||
void* locked;
|
||||
};
|
||||
|
||||
class VertexBufferGL: public GeometryBufferGL<VertexBuffer>
|
||||
{
|
||||
public:
|
||||
VertexBufferGL(Device* device, size_t elementSize, size_t elementCount, Usage usage);
|
||||
~VertexBufferGL();
|
||||
};
|
||||
|
||||
class IndexBufferGL: public GeometryBufferGL<IndexBuffer>
|
||||
{
|
||||
public:
|
||||
IndexBufferGL(Device* device, size_t elementSize, size_t elementCount, Usage usage);
|
||||
~IndexBufferGL();
|
||||
};
|
||||
|
||||
class GeometryGL: public Geometry
|
||||
{
|
||||
public:
|
||||
GeometryGL(Device* device, const shared_ptr<VertexLayout>& layout, const std::vector<shared_ptr<VertexBuffer> >& vertexBuffers, const shared_ptr<IndexBuffer>& indexBuffer, unsigned int baseVertexIndex);
|
||||
~GeometryGL();
|
||||
|
||||
void draw(Primitive primitive, unsigned int offset, unsigned int count);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
|
||||
private:
|
||||
unsigned int id;
|
||||
unsigned int indexElementSize;
|
||||
|
||||
unsigned int bindArrays();
|
||||
void unbindArrays(unsigned int mask);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef RBX_PLATFORM_IOS
|
||||
#include <OpenGLES/ES2/gl.h>
|
||||
#include <OpenGLES/ES2/glext.h>
|
||||
#ifdef __OBJC__
|
||||
#include <OpenGLES/EAGL.h>
|
||||
#endif
|
||||
|
||||
#define GLES
|
||||
#elif defined(__ANDROID__)
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
|
||||
#define GLES
|
||||
#else
|
||||
#include <GL/glew.h>
|
||||
|
||||
void glewInitRBX();
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
typedef struct __GLsync *GLsync;
|
||||
typedef uint64_t GLuint64;
|
||||
#endif
|
||||
|
||||
#ifdef GLES
|
||||
// Core GLES
|
||||
#define glClearDepth glClearDepthf
|
||||
|
||||
// OES_vertex_array_object
|
||||
GLvoid glBindVertexArray(GLuint array);
|
||||
GLvoid glDeleteVertexArrays(GLsizei n, const GLuint *arrays);
|
||||
GLvoid glGenVertexArrays(GLsizei n, GLuint *arrays);
|
||||
|
||||
// OES_mapbuffer
|
||||
#define GL_WRITE_ONLY 0x88B9
|
||||
GLvoid *glMapBuffer(GLenum target, GLenum access);
|
||||
GLboolean glUnmapBuffer(GLenum target);
|
||||
|
||||
// EXT_map_buffer_range
|
||||
GLvoid *glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
|
||||
#define GL_MAP_READ_BIT 0x0001
|
||||
#define GL_MAP_WRITE_BIT 0x0002
|
||||
#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004
|
||||
#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008
|
||||
#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010
|
||||
#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020
|
||||
|
||||
// EXT_texture_storage
|
||||
GLvoid glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
#define GL_LUMINANCE8 0x8040
|
||||
#define GL_LUMINANCE8_ALPHA8 0x8045
|
||||
|
||||
// GL_OES_packed_depth_stencil
|
||||
#define GL_DEPTH_STENCIL 0x84F9
|
||||
#define GL_UNSIGNED_INT_24_8 0x84FA
|
||||
#define GL_DEPTH24_STENCIL8 0x88F0
|
||||
|
||||
// OES_rgb8_rgba8
|
||||
#define GL_RGBA8 0x8058
|
||||
|
||||
// OES_texture_half_float
|
||||
#define GL_HALF_FLOAT 0x8D61
|
||||
|
||||
// EXT_color_buffer_half_float
|
||||
#define GL_RGBA16F 0x881A
|
||||
|
||||
// OES_texture_3D
|
||||
#define GL_TEXTURE_3D 0x806F
|
||||
GLvoid glTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
GLvoid glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels);
|
||||
GLvoid glTexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
|
||||
|
||||
// GLES 3.0 formats
|
||||
#define GL_RED 0x1903
|
||||
#define GL_RG 0x8227
|
||||
#define GL_R8 0x8229
|
||||
#define GL_RG8 0x822B
|
||||
#define GL_RG16 0x822C
|
||||
|
||||
// GLES 3.0 buffer objects
|
||||
#define GL_DYNAMIC_COPY 0x88EA
|
||||
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
|
||||
|
||||
// GLES 3.0 sync objects
|
||||
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
||||
#define GL_ALREADY_SIGNALED 0x911A
|
||||
#define GL_TIMEOUT_EXPIRED 0x911B
|
||||
#define GL_CONDITION_SATISFIED 0x911C
|
||||
#define GL_WAIT_FAILED 0x911D
|
||||
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
|
||||
GLsync glFenceSync(GLenum condition, GLbitfield flags);
|
||||
void glDeleteSync(GLsync sync);
|
||||
GLenum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||
void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||
|
||||
// GLES 3.0 MSAA
|
||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
||||
#define GL_MAX_SAMPLES 0x8D57
|
||||
GLvoid glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
GLvoid glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
|
||||
// GLES 3.0 invalidate FBO
|
||||
GLvoid glInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments);
|
||||
#endif
|
||||
|
||||
// EXT_texture_compression_s3tc
|
||||
#ifndef GL_EXT_texture_compression_s3tc
|
||||
#define GL_EXT_texture_compression_s3tc 1
|
||||
|
||||
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
||||
#endif
|
||||
|
||||
// IMG_texture_compression_pvrtc
|
||||
#ifndef GL_IMG_texture_compression_pvrtc
|
||||
#define GL_IMG_texture_compression_pvrtc 1
|
||||
|
||||
#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
|
||||
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
|
||||
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
|
||||
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
|
||||
#endif
|
||||
|
||||
#ifndef GL_ETC1_RGB8_OES
|
||||
#define GL_ETC1_RGB8_OES (0x8D64)
|
||||
#endif
|
||||
|
||||
// APPLE_texture_max_level, GLES 3.0
|
||||
#ifndef GL_TEXTURE_MAX_LEVEL
|
||||
#define GL_TEXTURE_MAX_LEVEL 0x813D
|
||||
#endif
|
||||
@@ -0,0 +1,551 @@
|
||||
#include "ShaderGL.h"
|
||||
|
||||
#include "GeometryGL.h"
|
||||
#include "DeviceGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
LOGGROUP(Graphics)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
static int getShaderParameter(GLuint shader, GLenum pname)
|
||||
{
|
||||
GLint result;
|
||||
glGetShaderiv(shader, pname, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getProgramParameter(GLuint program, GLenum pname)
|
||||
{
|
||||
GLint result;
|
||||
glGetProgramiv(program, pname, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename GetParameter, typename GetLog>
|
||||
static std::string getInfoLog(unsigned int id, GetParameter getParameter, GetLog getLog)
|
||||
{
|
||||
int infoLength = getParameter(id, GL_INFO_LOG_LENGTH);
|
||||
|
||||
if (infoLength > 0)
|
||||
{
|
||||
std::vector<char> buffer(infoLength);
|
||||
|
||||
getLog(id, infoLength, NULL, &buffer[0]);
|
||||
|
||||
return std::string(&buffer[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename GetParameter, typename GetLog>
|
||||
static void dumpInfoLog(unsigned int id, GetParameter getParameter, GetLog getLog)
|
||||
{
|
||||
std::string log = getInfoLog(id, getParameter, getLog);
|
||||
|
||||
// Intel and AMD drivers like to say "No errors" for every shader.
|
||||
if (strstr(log.c_str(), "warn") || strstr(log.c_str(), "Warn") || strstr(log.c_str(), "WARN"))
|
||||
{
|
||||
FASTLOG2(FLog::Graphics, "Shader %d has a non-empty infolog (length %d)", id, log.length());
|
||||
|
||||
ShaderProgram::dumpToFLog(log, FLog::Graphics);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int compileShader(const std::string& source, GLenum type)
|
||||
{
|
||||
unsigned int id = glCreateShader(type);
|
||||
RBXASSERT(id);
|
||||
|
||||
int length = source.length();
|
||||
const char* data = source.c_str();
|
||||
|
||||
glShaderSource(id, 1, &data, &length);
|
||||
|
||||
glCompileShader(id);
|
||||
|
||||
if (getShaderParameter(id, GL_COMPILE_STATUS) != 1)
|
||||
{
|
||||
std::string infoLog = getInfoLog(id, getShaderParameter, glGetShaderInfoLog);
|
||||
|
||||
glDeleteShader(id);
|
||||
|
||||
throw std::runtime_error(infoLog);
|
||||
}
|
||||
|
||||
dumpInfoLog(id, getShaderParameter, glGetShaderInfoLog);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static unsigned int parseAttribs(const std::string& source, const char* prefix)
|
||||
{
|
||||
std::set<std::string> attribs;
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < source.length())
|
||||
{
|
||||
std::string::size_type start = source.find(prefix, offset);
|
||||
if (start == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string::size_type eol = source.find('\n', start);
|
||||
if (eol == std::string::npos)
|
||||
break;
|
||||
|
||||
offset = eol;
|
||||
|
||||
std::string::size_type semi = source.find(';', start);
|
||||
std::string::size_type space = source.find_last_of(' ', eol);
|
||||
|
||||
if ((start == 0 || source[start - 1] == '\n') && semi != std::string::npos && space != std::string::npos && space < semi)
|
||||
attribs.insert(source.substr(space + 1, semi - space - 1));
|
||||
}
|
||||
|
||||
unsigned int result = 0;
|
||||
|
||||
for (unsigned int attr = 0; attr < 16; ++attr)
|
||||
{
|
||||
const char* name = VertexLayoutGL::getShaderAttributeName(attr);
|
||||
|
||||
if (name && attribs.count(name))
|
||||
result |= 1 << attr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void applyAttribs(unsigned int id, unsigned int attribMask)
|
||||
{
|
||||
for (unsigned int attr = 0; attr < 16; ++attr)
|
||||
if (attribMask & (1 << attr))
|
||||
{
|
||||
const char* name = VertexLayoutGL::getShaderAttributeName(attr);
|
||||
RBXASSERT(name);
|
||||
glBindAttribLocation(id, attr, name);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<FragmentShaderGL::Sampler> parseSamplers(const std::string& source)
|
||||
{
|
||||
std::vector<FragmentShaderGL::Sampler> result;
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < source.length())
|
||||
{
|
||||
std::string::size_type start = source.find("//$$", offset);
|
||||
if (start == std::string::npos)
|
||||
return result;
|
||||
|
||||
std::string::size_type equals = source.find('=', start);
|
||||
if (equals == std::string::npos)
|
||||
return result;
|
||||
|
||||
std::string::size_type end = source.find('\n', equals);
|
||||
if (end == std::string::npos)
|
||||
return result;
|
||||
|
||||
std::string name(source.begin() + start + 4, source.begin() + equals);
|
||||
std::string value(source.begin() + equals + 1, source.begin() + end);
|
||||
|
||||
if (!value.empty() && value[0] == 's')
|
||||
{
|
||||
FragmentShaderGL::Sampler entry = {name, atoi(value.c_str() + 1)};
|
||||
|
||||
result.push_back(entry);
|
||||
}
|
||||
|
||||
offset = end;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getSamplerLocation(unsigned int id, const std::string& name)
|
||||
{
|
||||
int location;
|
||||
|
||||
if ((location = glGetUniformLocation(id, name.c_str())) >= 0)
|
||||
return location;
|
||||
|
||||
if ((location = glGetUniformLocation(id, ("xlu_" + name).c_str())) >= 0)
|
||||
return location;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static unsigned int applySamplers(unsigned int id, const std::vector<FragmentShaderGL::Sampler>& samplers)
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
|
||||
glUseProgram(id);
|
||||
|
||||
for (size_t i = 0; i < samplers.size(); ++i)
|
||||
{
|
||||
int location = getSamplerLocation(id, samplers[i].name);
|
||||
|
||||
if (location >= 0)
|
||||
{
|
||||
glUniform1i(location, samplers[i].slot);
|
||||
|
||||
mask |= 1 << samplers[i].slot;
|
||||
}
|
||||
}
|
||||
|
||||
glUseProgram(0);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static ShaderProgramGL::Uniform::Type getUniformType(GLenum type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case GL_FLOAT:
|
||||
return ShaderProgramGL::Uniform::Type_Float;
|
||||
case GL_FLOAT_VEC2:
|
||||
return ShaderProgramGL::Uniform::Type_Float2;
|
||||
case GL_FLOAT_VEC3:
|
||||
return ShaderProgramGL::Uniform::Type_Float3;
|
||||
case GL_FLOAT_VEC4:
|
||||
return ShaderProgramGL::Uniform::Type_Float4;
|
||||
case GL_FLOAT_MAT4:
|
||||
return ShaderProgramGL::Uniform::Type_Float4x4;
|
||||
default:
|
||||
return ShaderProgramGL::Uniform::Type_Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::map<std::string, ShaderProgramGL::Uniform> UniformTable;
|
||||
|
||||
static UniformTable extractUniforms(unsigned int id)
|
||||
{
|
||||
int uniformCount = getProgramParameter(id, GL_ACTIVE_UNIFORMS);
|
||||
int uniformMaxLength = getProgramParameter(id, GL_ACTIVE_UNIFORM_MAX_LENGTH);
|
||||
|
||||
std::vector<char> uniformName(uniformMaxLength + 1);
|
||||
|
||||
UniformTable result;
|
||||
|
||||
for (int i = 0; i < uniformCount; ++i)
|
||||
{
|
||||
GLint size;
|
||||
GLenum type;
|
||||
glGetActiveUniform(id, i, uniformMaxLength, NULL, &size, &type, &uniformName[0]);
|
||||
|
||||
ShaderProgramGL::Uniform::Type uniformType = getUniformType(type);
|
||||
|
||||
int location = glGetUniformLocation(id, &uniformName[0]);
|
||||
|
||||
if (location >= 0 && uniformType != ShaderProgramGL::Uniform::Type_Unknown)
|
||||
{
|
||||
ShaderProgramGL::Uniform uniform = {location, uniformType, static_cast<unsigned int>(size), static_cast<unsigned int>(0)};
|
||||
|
||||
std::string name(&uniformName[0]);
|
||||
|
||||
// Remove xlu_ prefix for entrypoint-local uniforms
|
||||
if (name.compare(0, 4, "xlu_") == 0)
|
||||
name.erase(name.begin(), name.begin() + 4);
|
||||
|
||||
// Remove [0] from arrays
|
||||
std::string::size_type index = name.find('[');
|
||||
|
||||
if (index != std::string::npos)
|
||||
name.erase(name.begin() + index, name.end());
|
||||
|
||||
result[name] = uniform;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const ShaderProgramGL::Uniform* findUniform(const UniformTable& ctab, const std::string& name)
|
||||
{
|
||||
UniformTable::const_iterator it = ctab.find(name);
|
||||
|
||||
return (it == ctab.end()) ? NULL : &it->second;
|
||||
}
|
||||
|
||||
static inline void transposeMatrix(float* dest, const float* source, const float* lastColumn)
|
||||
{
|
||||
dest[0] = source[0]; dest[1] = source[4]; dest[2] = source[8]; dest[3] = lastColumn[0];
|
||||
dest[4] = source[1]; dest[5] = source[5]; dest[6] = source[9]; dest[7] = lastColumn[1];
|
||||
dest[8] = source[2]; dest[9] = source[6]; dest[10] = source[10]; dest[11] = lastColumn[2];
|
||||
dest[12] = source[3]; dest[13] = source[7]; dest[14] = source[11]; dest[15] = lastColumn[3];
|
||||
}
|
||||
|
||||
VertexShaderGL::VertexShaderGL(Device* device, const std::string& source)
|
||||
: VertexShader(device)
|
||||
, id(0)
|
||||
, attribMask(0)
|
||||
{
|
||||
id = compileShader(source, GL_VERTEX_SHADER);
|
||||
|
||||
attribMask = parseAttribs(source, static_cast<DeviceGL*>(device)->getCapsGL().ext3 ? "in " : "attribute ");
|
||||
}
|
||||
|
||||
VertexShaderGL::~VertexShaderGL()
|
||||
{
|
||||
glDeleteShader(id);
|
||||
}
|
||||
|
||||
void VertexShaderGL::reloadBytecode(const std::vector<char>& bytecode)
|
||||
{
|
||||
throw std::runtime_error("Bytecode reloading is not supported");
|
||||
}
|
||||
|
||||
FragmentShaderGL::FragmentShaderGL(Device* device, const std::string& source)
|
||||
: FragmentShader(device)
|
||||
, id(0)
|
||||
{
|
||||
id = compileShader(source, GL_FRAGMENT_SHADER);
|
||||
|
||||
samplers = parseSamplers(source);
|
||||
}
|
||||
|
||||
FragmentShaderGL::~FragmentShaderGL()
|
||||
{
|
||||
glDeleteShader(id);
|
||||
}
|
||||
|
||||
void FragmentShaderGL::reloadBytecode(const std::vector<char>& bytecode)
|
||||
{
|
||||
throw std::runtime_error("Bytecode reloading is not supported");
|
||||
}
|
||||
|
||||
ShaderProgramGL::ShaderProgramGL(Device* device, const shared_ptr<VertexShader>& vertexShader, const shared_ptr<FragmentShader>& fragmentShader)
|
||||
: ShaderProgram(device, vertexShader, fragmentShader)
|
||||
, id(0)
|
||||
, uniformWorldMatrix(-1)
|
||||
, uniformWorldMatrixArray(-1)
|
||||
, cachedGlobalVersion(0)
|
||||
, maxWorldTransforms(0)
|
||||
, samplerMask(0)
|
||||
{
|
||||
id = glCreateProgram();
|
||||
RBXASSERT(id);
|
||||
|
||||
glAttachShader(id, static_cast<VertexShaderGL*>(vertexShader.get())->getId());
|
||||
glAttachShader(id, static_cast<FragmentShaderGL*>(fragmentShader.get())->getId());
|
||||
|
||||
// Setup attribute locations
|
||||
applyAttribs(id, static_cast<VertexShaderGL*>(vertexShader.get())->getAttribMask());
|
||||
|
||||
glLinkProgram(id);
|
||||
|
||||
if (getProgramParameter(id, GL_LINK_STATUS) != 1)
|
||||
{
|
||||
std::string infoLog = getInfoLog(id, getProgramParameter, glGetProgramInfoLog);
|
||||
|
||||
glDeleteProgram(id);
|
||||
|
||||
throw std::runtime_error(infoLog);
|
||||
}
|
||||
|
||||
dumpInfoLog(id, getProgramParameter, glGetProgramInfoLog);
|
||||
|
||||
// Make sure the program uses correct sampler slot assignments
|
||||
// Note: this resets current program to 0!
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedProgram();
|
||||
|
||||
samplerMask = applySamplers(id, static_cast<FragmentShaderGL*>(fragmentShader.get())->getSamplers());
|
||||
|
||||
// Populate constant tables
|
||||
const std::vector<ShaderGlobalConstant>& globalConstants = static_cast<DeviceGL*>(device)->getGlobalConstants();
|
||||
|
||||
UniformTable ctab = extractUniforms(id);
|
||||
|
||||
for (size_t i = 0; i < globalConstants.size(); ++i)
|
||||
if (const Uniform* uniform = findUniform(ctab, globalConstants[i].name))
|
||||
{
|
||||
Uniform u = *uniform;
|
||||
u.offset = globalConstants[i].offset;
|
||||
|
||||
globalUniforms.push_back(u);
|
||||
|
||||
ctab.erase(globalConstants[i].name);
|
||||
}
|
||||
|
||||
// Populate world matrix information
|
||||
if (const Uniform* uniform = findUniform(ctab, "WorldMatrix"))
|
||||
{
|
||||
RBXASSERT(uniform->type == Uniform::Type_Float4x4);
|
||||
|
||||
uniformWorldMatrix = uniform->location;
|
||||
maxWorldTransforms = 1;
|
||||
|
||||
ctab.erase("WorldMatrix");
|
||||
}
|
||||
|
||||
if (const Uniform* uniform = findUniform(ctab, "WorldMatrixArray"))
|
||||
{
|
||||
RBXASSERT(uniform->type == Uniform::Type_Float4);
|
||||
RBXASSERT(uniform->size % 3 == 0);
|
||||
|
||||
uniformWorldMatrixArray = uniform->location;
|
||||
maxWorldTransforms = uniform->size / 3;
|
||||
|
||||
ctab.erase("WorldMatrixArray");
|
||||
}
|
||||
|
||||
// Populate uniform information
|
||||
for (UniformTable::iterator it = ctab.begin(); it != ctab.end(); ++it)
|
||||
{
|
||||
uniforms.push_back(std::make_pair(it->first, it->second));
|
||||
}
|
||||
}
|
||||
|
||||
ShaderProgramGL::~ShaderProgramGL()
|
||||
{
|
||||
glDeleteProgram(id);
|
||||
|
||||
// Shader program destruction invalidates currently bound program id
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedProgram();
|
||||
}
|
||||
|
||||
int ShaderProgramGL::getConstantHandle(const char* name) const
|
||||
{
|
||||
for (size_t i = 0; i < uniforms.size(); ++i)
|
||||
if (uniforms[i].first == name)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int ShaderProgramGL::getMaxWorldTransforms() const
|
||||
{
|
||||
return maxWorldTransforms;
|
||||
}
|
||||
|
||||
unsigned int ShaderProgramGL::getSamplerMask() const
|
||||
{
|
||||
return samplerMask;
|
||||
}
|
||||
|
||||
void ShaderProgramGL::bind(const void* globalData, unsigned int globalVersion, ShaderProgramGL** cache)
|
||||
{
|
||||
if (*cache != this)
|
||||
{
|
||||
*cache = this;
|
||||
|
||||
glUseProgram(id);
|
||||
}
|
||||
|
||||
if (cachedGlobalVersion != globalVersion)
|
||||
{
|
||||
cachedGlobalVersion = globalVersion;
|
||||
|
||||
for (size_t i = 0; i < globalUniforms.size(); ++i)
|
||||
{
|
||||
const Uniform& u = globalUniforms[i];
|
||||
|
||||
const float* data = reinterpret_cast<const float*>(static_cast<const char*>(globalData) + u.offset);
|
||||
|
||||
switch (u.type)
|
||||
{
|
||||
case Uniform::Type_Float:
|
||||
glUniform1fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float2:
|
||||
glUniform2fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float3:
|
||||
glUniform3fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float4:
|
||||
glUniform4fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float4x4:
|
||||
{
|
||||
float matrix[16];
|
||||
transposeMatrix(matrix, data, data + 12);
|
||||
|
||||
glUniformMatrix4fv(u.location, 1, false, matrix);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderProgramGL::setWorldTransforms4x3(const float* data, size_t matrixCount)
|
||||
{
|
||||
if (matrixCount == 0)
|
||||
return;
|
||||
|
||||
if (uniformWorldMatrix >= 0)
|
||||
{
|
||||
static const float lastColumn[] = {0, 0, 0, 1};
|
||||
|
||||
float matrix[16];
|
||||
transposeMatrix(matrix, data, lastColumn);
|
||||
|
||||
glUniformMatrix4fv(uniformWorldMatrix, 1, false, matrix);
|
||||
}
|
||||
|
||||
if (uniformWorldMatrixArray >= 0)
|
||||
{
|
||||
RBXASSERT(matrixCount <= maxWorldTransforms);
|
||||
|
||||
glUniform4fv(uniformWorldMatrixArray, matrixCount * 3, data);
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderProgramGL::setConstant(int handle, const float* data, size_t vectorCount)
|
||||
{
|
||||
if (handle < 0)
|
||||
return;
|
||||
|
||||
RBXASSERT(static_cast<size_t>(handle) < uniforms.size());
|
||||
|
||||
const Uniform& u = uniforms[handle].second;
|
||||
|
||||
switch (u.type)
|
||||
{
|
||||
case Uniform::Type_Float:
|
||||
RBXASSERT(vectorCount == 1);
|
||||
glUniform1fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float2:
|
||||
RBXASSERT(vectorCount == 1);
|
||||
glUniform2fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float3:
|
||||
RBXASSERT(vectorCount == 1);
|
||||
glUniform3fv(u.location, 1, data);
|
||||
break;
|
||||
|
||||
case Uniform::Type_Float4:
|
||||
RBXASSERT(vectorCount > 0 && vectorCount <= u.size);
|
||||
glUniform4fv(u.location, vectorCount, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
RBXASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "GfxCore/Shader.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class VertexShaderGL: public VertexShader
|
||||
{
|
||||
public:
|
||||
VertexShaderGL(Device* device, const std::string& source);
|
||||
~VertexShaderGL();
|
||||
|
||||
virtual void reloadBytecode(const std::vector<char>& bytecode);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
|
||||
unsigned int getAttribMask() const { return attribMask; }
|
||||
|
||||
private:
|
||||
unsigned int id;
|
||||
unsigned int attribMask;
|
||||
};
|
||||
|
||||
class FragmentShaderGL: public FragmentShader
|
||||
{
|
||||
public:
|
||||
struct Sampler
|
||||
{
|
||||
std::string name;
|
||||
int slot;
|
||||
};
|
||||
|
||||
FragmentShaderGL(Device* device, const std::string& source);
|
||||
~FragmentShaderGL();
|
||||
|
||||
virtual void reloadBytecode(const std::vector<char>& bytecode);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
|
||||
const std::vector<Sampler>& getSamplers() const { return samplers; }
|
||||
|
||||
private:
|
||||
unsigned int id;
|
||||
std::vector<Sampler> samplers;
|
||||
};
|
||||
|
||||
class ShaderProgramGL: public ShaderProgram
|
||||
{
|
||||
public:
|
||||
struct Uniform
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Type_Unknown,
|
||||
|
||||
Type_Float,
|
||||
Type_Float2,
|
||||
Type_Float3,
|
||||
Type_Float4,
|
||||
Type_Float4x4,
|
||||
|
||||
Type_Count
|
||||
};
|
||||
|
||||
int location;
|
||||
Type type;
|
||||
unsigned int size;
|
||||
|
||||
unsigned int offset;
|
||||
};
|
||||
|
||||
ShaderProgramGL(Device* device, const shared_ptr<VertexShader>& vertexShader, const shared_ptr<FragmentShader>& fragmentShader);
|
||||
~ShaderProgramGL();
|
||||
|
||||
virtual int getConstantHandle(const char* name) const;
|
||||
|
||||
virtual unsigned int getMaxWorldTransforms() const;
|
||||
virtual unsigned int getSamplerMask() const;
|
||||
|
||||
void bind(const void* globalData, unsigned int globalVersion, ShaderProgramGL** cache);
|
||||
|
||||
void setWorldTransforms4x3(const float* data, size_t matrixCount);
|
||||
void setConstant(int handle, const float* data, size_t vectorCount);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
|
||||
private:
|
||||
unsigned int id;
|
||||
|
||||
int uniformWorldMatrix;
|
||||
int uniformWorldMatrixArray;
|
||||
|
||||
unsigned int cachedGlobalVersion;
|
||||
|
||||
std::vector<Uniform> globalUniforms;
|
||||
std::vector<std::pair<std::string, Uniform> > uniforms;
|
||||
|
||||
unsigned int maxWorldTransforms;
|
||||
unsigned int samplerMask;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
#include "TextureGL.h"
|
||||
|
||||
#include "DeviceGL.h"
|
||||
#include "FramebufferGL.h"
|
||||
|
||||
#include "HeadersGL.h"
|
||||
|
||||
LOGGROUP(Graphics)
|
||||
|
||||
FASTFLAG(GraphicsTextureCommitChanges)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
static const GLenum gTextureTargetGL[Texture::Type_Count] =
|
||||
{
|
||||
GL_TEXTURE_2D,
|
||||
GL_TEXTURE_3D,
|
||||
GL_TEXTURE_CUBE_MAP
|
||||
};
|
||||
|
||||
struct TextureFormatGL
|
||||
{
|
||||
GLenum internalFormat;
|
||||
GLenum dataFormat;
|
||||
GLenum dataType;
|
||||
};
|
||||
|
||||
static const TextureFormatGL gTextureFormatGL[Texture::Format_Count] =
|
||||
{
|
||||
{ GL_R8, GL_RED, GL_UNSIGNED_BYTE },
|
||||
{ GL_RG8, GL_RG, GL_UNSIGNED_BYTE },
|
||||
{ GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 },
|
||||
{ GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE },
|
||||
{ GL_RG16, GL_RG, GL_UNSIGNED_SHORT },
|
||||
{ GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT },
|
||||
{ GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 0, 0 },
|
||||
{ GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 0, 0 },
|
||||
{ GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 0, 0 },
|
||||
{ GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 0, 0 },
|
||||
{ GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, 0, 0 },
|
||||
{ GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 0, 0 },
|
||||
{ GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 0, 0 },
|
||||
{ GL_ETC1_RGB8_OES, 0, 0 },
|
||||
{ GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT },
|
||||
{ GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 },
|
||||
};
|
||||
|
||||
const TextureFormatGL& getTextureFormatGL(Texture::Format format, bool ext3)
|
||||
{
|
||||
if (!ext3 && format == Texture::Format_L8)
|
||||
{
|
||||
static const TextureFormatGL result = { GL_LUMINANCE8, GL_LUMINANCE, GL_UNSIGNED_BYTE };
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!ext3 && format == Texture::Format_LA8)
|
||||
{
|
||||
static const TextureFormatGL result = { GL_LUMINANCE8_ALPHA8, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE };
|
||||
return result;
|
||||
}
|
||||
|
||||
return gTextureFormatGL[format];
|
||||
}
|
||||
|
||||
static const GLenum gSamplerFilterMinGL[SamplerState::Filter_Count][2] =
|
||||
{
|
||||
{ GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST },
|
||||
{ GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR },
|
||||
{ GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR }
|
||||
};
|
||||
|
||||
static const GLenum gSamplerFilterMagGL[SamplerState::Filter_Count] =
|
||||
{
|
||||
GL_NEAREST,
|
||||
GL_LINEAR,
|
||||
GL_LINEAR
|
||||
};
|
||||
|
||||
static const GLenum gSamplerAddressGL[SamplerState::Address_Count] =
|
||||
{
|
||||
GL_REPEAT,
|
||||
GL_CLAMP_TO_EDGE
|
||||
};
|
||||
|
||||
static bool doesFormatSupportPartialUpload(Texture::Format format)
|
||||
{
|
||||
return format != Texture::Format_ETC1;
|
||||
}
|
||||
|
||||
static unsigned int createTexture(Texture::Type type, Texture::Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, const DeviceCapsGL& caps)
|
||||
{
|
||||
GLenum target = gTextureTargetGL[type];
|
||||
const TextureFormatGL& formatGl = getTextureFormatGL(format, caps.ext3);
|
||||
|
||||
unsigned int id = 0;
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
glGenTextures(1, &id);
|
||||
glBindTexture(target, id);
|
||||
|
||||
if (caps.extTextureStorage)
|
||||
{
|
||||
if (type == Texture::Type_3D)
|
||||
{
|
||||
glTexStorage3D(target, mipLevels, formatGl.internalFormat, width, height, depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexStorage2D(target, mipLevels, formatGl.internalFormat, width, height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Is this correct? There's a subtle difference here between desktop GL and GLES
|
||||
#ifdef GLES
|
||||
GLenum internalFormat = formatGl.dataFormat;
|
||||
#else
|
||||
GLenum internalFormat = formatGl.internalFormat;
|
||||
#endif
|
||||
|
||||
unsigned int faces = (type == Texture::Type_Cube) ? 6 : 1;
|
||||
|
||||
for (unsigned int index = 0; index < faces; ++index)
|
||||
{
|
||||
GLenum faceTarget = (type == Texture::Type_Cube) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + index : target;
|
||||
|
||||
for (unsigned int mip = 0; mip < mipLevels; ++mip)
|
||||
{
|
||||
unsigned int mipWidth = Texture::getMipSide(width, mip);
|
||||
unsigned int mipHeight = Texture::getMipSide(height, mip);
|
||||
unsigned int mipDepth = Texture::getMipSide(depth, mip);
|
||||
|
||||
if (type == Texture::Type_3D)
|
||||
{
|
||||
glTexImage3D(faceTarget, mip, internalFormat, mipWidth, mipHeight, mipDepth, 0, formatGl.dataFormat, formatGl.dataType, NULL);
|
||||
}
|
||||
else if (Texture::isFormatCompressed(format))
|
||||
{
|
||||
unsigned int size = Texture::getImageSize(format, mipWidth, mipHeight);
|
||||
|
||||
// glCompressedTexImage does not accept NULL as a valid pointer
|
||||
std::vector<char> data(size);
|
||||
|
||||
glCompressedTexImage2D(faceTarget, mip, formatGl.internalFormat, mipWidth, mipHeight, 0, size, &data[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexImage2D(faceTarget, mip, internalFormat, mipWidth, mipHeight, 0, formatGl.dataFormat, formatGl.dataType, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (caps.supportsTexturePartialMipChain)
|
||||
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, mipLevels - 1);
|
||||
else
|
||||
RBXASSERT(mipLevels == 1 || mipLevels == Texture::getMaxMipCount(width, height, depth));
|
||||
|
||||
glBindTexture(target, 0);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static void uploadTexture(unsigned int id, Texture::Type type, Texture::Format format, unsigned int index, unsigned int mip, const TextureRegion& region, bool entireRegion, const void* data, const DeviceCapsGL& caps)
|
||||
{
|
||||
GLenum target = gTextureTargetGL[type];
|
||||
GLenum faceTarget = (type == Texture::Type_Cube) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + index : target;
|
||||
const TextureFormatGL& formatGl = getTextureFormatGL(format, caps.ext3);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(target, id);
|
||||
|
||||
bool needsCustomAlignment = (!Texture::isFormatCompressed(format) && Texture::getFormatBits(format) < 32);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, needsCustomAlignment ? 1 : 4);
|
||||
|
||||
if (type == Texture::Type_3D)
|
||||
{
|
||||
glTexSubImage3D(faceTarget, mip, region.x, region.y, region.z, region.width, region.height, region.depth, formatGl.dataFormat, formatGl.dataType, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Texture::isFormatCompressed(format))
|
||||
{
|
||||
unsigned int size = Texture::getImageSize(format, region.width, region.height) * region.depth;
|
||||
|
||||
if (entireRegion && !doesFormatSupportPartialUpload(format) && !caps.extTextureStorage)
|
||||
{
|
||||
glCompressedTexImage2D(faceTarget, mip, formatGl.internalFormat, region.width, region.height, 0, size, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
glCompressedTexSubImage2D(faceTarget, mip, region.x, region.y, region.width, region.height, formatGl.internalFormat, size, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexSubImage2D(faceTarget, mip, region.x, region.y, region.width, region.height, formatGl.dataFormat, formatGl.dataType, data);
|
||||
}
|
||||
}
|
||||
|
||||
glBindTexture(target, 0);
|
||||
}
|
||||
|
||||
TextureGL::TextureGL(Device* device, Type type, Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Usage usage)
|
||||
: Texture(device, type, format, width, height, depth, mipLevels, usage)
|
||||
, id(0)
|
||||
, cachedState(SamplerState::Filter_Count) // initialize state to invalid value to force a cache miss on the next setup
|
||||
, external(false)
|
||||
, scratchId(0)
|
||||
, scratchOffset(0)
|
||||
, scratchSize(0)
|
||||
{
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(device)->getCapsGL();
|
||||
|
||||
const TextureFormatGL& formatGl = getTextureFormatGL(format, caps.ext3);
|
||||
|
||||
if (!formatGl.internalFormat)
|
||||
throw RBX::runtime_error("Unsupported format: %d (platform)", format);
|
||||
|
||||
if ((format == Format_RGBA16F) && !caps.supportsTextureHalfFloat)
|
||||
throw RBX::runtime_error("Unsupported format: %d (caps)", format);
|
||||
|
||||
if ((format == Format_BC1 || format == Format_BC2 || format == Format_BC3) && !caps.supportsTextureDXT)
|
||||
throw RBX::runtime_error("Unsupported format: %d (caps)", format);
|
||||
|
||||
if ((format == Format_PVRTC_RGB2 || format == Format_PVRTC_RGBA2 || format == Format_PVRTC_RGB4 || format == Format_PVRTC_RGBA4) && !caps.supportsTexturePVR)
|
||||
throw RBX::runtime_error("Unsupported format: %d (caps)", format);
|
||||
|
||||
if (format == Format_ETC1 && !caps.supportsTextureETC1)
|
||||
throw RBX::runtime_error("Unsupported format: %d (capse)", format);
|
||||
|
||||
if (!caps.supportsTextureNPOT && ((width & (width - 1)) || (height & (height - 1)) || (depth & (depth - 1))) && (usage != Usage_Renderbuffer || mipLevels > 1))
|
||||
throw RBX::runtime_error("Unsupported dimensions: %dx%dx%d (NPOT)", width, height, depth);
|
||||
|
||||
if (!caps.supportsTexture3D && type == Type_3D)
|
||||
throw RBX::runtime_error("Unsupported type: 3D");
|
||||
|
||||
if (FFlag::GraphicsTextureCommitChanges && usage == Usage_Dynamic && isFormatCompressed(format))
|
||||
throw RBX::runtime_error("Unsupported format: compressed formats are not supported for dynamic textures");
|
||||
|
||||
// createTexture poisons the binding on the first stage
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedTextureStage(0);
|
||||
|
||||
id = createTexture(type, format, width, height, depth, mipLevels, caps);
|
||||
|
||||
if (FFlag::GraphicsTextureCommitChanges && usage == Usage_Dynamic && caps.ext3)
|
||||
{
|
||||
scratchSize = getTextureSize(type, format, width, height, depth, mipLevels);
|
||||
|
||||
glGenBuffers(1, &scratchId);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, scratchId);
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, scratchSize, NULL, GL_DYNAMIC_COPY);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TextureGL::TextureGL(Device* device, Type type, Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Usage usage, unsigned int id)
|
||||
: Texture(device, type, format, width, height, depth, mipLevels, usage)
|
||||
, cachedState(SamplerState::Filter_Count) // initialize state to invalid value to force a cache miss on the next setup
|
||||
, id(id)
|
||||
, external(true)
|
||||
, scratchId(0)
|
||||
, scratchOffset(0)
|
||||
, scratchSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
TextureGL::~TextureGL()
|
||||
{
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedTexture(this);
|
||||
|
||||
if (scratchId)
|
||||
glDeleteBuffers(1, &scratchId);
|
||||
|
||||
if (!external)
|
||||
glDeleteTextures(1, &id);
|
||||
}
|
||||
|
||||
void TextureGL::upload(unsigned int index, unsigned int mip, const TextureRegion& region, const void* data, unsigned int size)
|
||||
{
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(device)->getCapsGL();
|
||||
|
||||
RBXASSERT(index < (type == Type_Cube ? 6u : 1u));
|
||||
|
||||
unsigned int mipWidth = getMipSide(width, mip);
|
||||
unsigned int mipHeight = getMipSide(height, mip);
|
||||
unsigned int mipDepth = getMipSide(depth, mip);
|
||||
|
||||
RBXASSERT(mip < mipLevels);
|
||||
RBXASSERT(region.x + region.width <= mipWidth);
|
||||
RBXASSERT(region.y + region.height <= mipHeight);
|
||||
RBXASSERT(region.z + region.depth <= mipDepth);
|
||||
|
||||
RBXASSERT(size == getImageSize(format, region.width, region.height) * region.depth);
|
||||
|
||||
if (supportsLocking())
|
||||
{
|
||||
unsigned int bpp = getFormatBits(format) / 8;
|
||||
|
||||
LockResult lr = lock(index, mip, region);
|
||||
|
||||
for (unsigned int z = 0; z < region.depth; ++z)
|
||||
for (unsigned int y = 0; y < region.height; ++y)
|
||||
{
|
||||
unsigned int sourceOffset = (region.width * region.height * z + region.width * y) * bpp;
|
||||
unsigned int targetOffset = lr.slicePitch * z + lr.rowPitch * y;
|
||||
|
||||
memcpy(static_cast<char*>(lr.data) + targetOffset, static_cast<const char*>(data) + sourceOffset, region.width * bpp);
|
||||
}
|
||||
|
||||
unlock(index, mip);
|
||||
}
|
||||
else
|
||||
{
|
||||
// uploadTexture poisons the binding on the first stage
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedTextureStage(0);
|
||||
|
||||
bool entireRegion = (region.x == 0 && region.y == 0 && region.width == mipWidth && region.height == mipHeight);
|
||||
|
||||
uploadTexture(id, type, format, index, mip, region, entireRegion, data, caps);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef GLES
|
||||
bool TextureGL::download(unsigned int index, unsigned int mip, void* data, unsigned int size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
bool TextureGL::download(unsigned int index, unsigned int mip, void* data, unsigned int size)
|
||||
{
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(device)->getCapsGL();
|
||||
|
||||
GLenum target = gTextureTargetGL[type];
|
||||
const TextureFormatGL& formatGl = getTextureFormatGL(format, caps.ext3);
|
||||
|
||||
RBXASSERT(index < (type == Type_Cube ? 6u : 1u));
|
||||
GLenum faceTarget = (type == Type_Cube) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + index : target;
|
||||
|
||||
unsigned int mipWidth = getMipSide(width, mip);
|
||||
unsigned int mipHeight = getMipSide(height, mip);
|
||||
unsigned int mipDepth = getMipSide(depth, mip);
|
||||
|
||||
RBXASSERT(mip < mipLevels);
|
||||
RBXASSERT(size == getImageSize(format, mipWidth, mipHeight) * mipDepth);
|
||||
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedTextureStage(0);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(target, id);
|
||||
|
||||
bool needsCustomAlignment = (!isFormatCompressed(format) && getFormatBits(format) < 32);
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, needsCustomAlignment ? 1 : 4);
|
||||
|
||||
if (isFormatCompressed(format))
|
||||
glGetCompressedTexImage(faceTarget, mip, data);
|
||||
else
|
||||
glGetTexImage(faceTarget, mip, formatGl.dataFormat, formatGl.dataType, data);
|
||||
|
||||
glBindTexture(target, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool TextureGL::supportsLocking() const
|
||||
{
|
||||
if (FFlag::GraphicsTextureCommitChanges)
|
||||
return scratchId != 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Texture::LockResult TextureGL::lock(unsigned int index, unsigned int mip, const TextureRegion& region)
|
||||
{
|
||||
if (!scratchId)
|
||||
{
|
||||
Texture::LockResult result = {0, 0, 0};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
RBXASSERT(index < (type == Type_Cube ? 6u : 1u));
|
||||
|
||||
unsigned int mipWidth = getMipSide(width, mip);
|
||||
unsigned int mipHeight = getMipSide(height, mip);
|
||||
unsigned int mipDepth = getMipSide(depth, mip);
|
||||
|
||||
RBXASSERT(mip < mipLevels);
|
||||
RBXASSERT(region.x + region.width <= mipWidth);
|
||||
RBXASSERT(region.y + region.height <= mipHeight);
|
||||
RBXASSERT(region.z + region.depth <= mipDepth);
|
||||
|
||||
unsigned int size = getImageSize(format, region.width, region.height) * region.depth;
|
||||
unsigned int flags = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
|
||||
|
||||
if (scratchOffset + size > scratchSize)
|
||||
{
|
||||
commitChanges();
|
||||
|
||||
scratchOffset = 0;
|
||||
flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, scratchId);
|
||||
void* data = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, scratchOffset, size, flags);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
Change change = { index, mip, region, scratchOffset };
|
||||
|
||||
scratchOffset += size;
|
||||
pendingChanges.push_back(change);
|
||||
|
||||
Texture::LockResult result = {data, getImageSize(format, region.width, 1), getImageSize(format, region.width, region.height) };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextureGL::unlock(unsigned int index, unsigned int mip)
|
||||
{
|
||||
if (!scratchId)
|
||||
return;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, scratchId);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
shared_ptr<Renderbuffer> TextureGL::getRenderbuffer(unsigned int index, unsigned int mip)
|
||||
{
|
||||
RBXASSERT(usage == Usage_Renderbuffer);
|
||||
|
||||
RBXASSERT(mip == 0);
|
||||
RBXASSERT(index < (type == Type_Cube ? 6u : 1u));
|
||||
|
||||
weak_ptr<Renderbuffer>& slot = renderBuffers[std::make_pair(index,mip)];
|
||||
shared_ptr<Renderbuffer> rb = slot.lock();
|
||||
|
||||
if (!rb)
|
||||
{
|
||||
GLenum target = (type == Texture::Type_Cube) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + index : gTextureTargetGL[type];
|
||||
|
||||
rb.reset(new RenderbufferGL(device, shared_from_this(), target));
|
||||
slot = rb;
|
||||
}
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
void TextureGL::commitChanges()
|
||||
{
|
||||
if (!FFlag::GraphicsTextureCommitChanges)
|
||||
return;
|
||||
|
||||
const DeviceCapsGL& caps = static_cast<DeviceGL*>(device)->getCapsGL();
|
||||
|
||||
if (pendingChanges.empty())
|
||||
return;
|
||||
|
||||
// uploadTexture poisons the binding on the first stage
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedTextureStage(0);
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, scratchId);
|
||||
|
||||
for (auto& change: pendingChanges)
|
||||
{
|
||||
RBXASSERT(change.scratchOffset < scratchOffset);
|
||||
|
||||
bool entireRegion = false; // this is only important for compressed formats that we don't support
|
||||
|
||||
uploadTexture(id, type, format, change.index, change.mip, change.region, entireRegion, reinterpret_cast<void*>(change.scratchOffset), caps);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
pendingChanges.clear();
|
||||
}
|
||||
|
||||
void TextureGL::generateMipmaps()
|
||||
{
|
||||
RBXASSERT(usage != Usage_Dynamic);
|
||||
|
||||
if (mipLevels <= 1)
|
||||
return;
|
||||
|
||||
static_cast<DeviceGL*>(device)->getImmediateContextGL()->invalidateCachedTextureStage(0);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(gTextureTargetGL[type], id);
|
||||
|
||||
glGenerateMipmap(gTextureTargetGL[type]);
|
||||
|
||||
glBindTexture(gTextureTargetGL[type], 0);
|
||||
}
|
||||
|
||||
void TextureGL::bind(unsigned int stage, const SamplerState& state, TextureGL** cache)
|
||||
{
|
||||
if (*cache != this || cachedState != state)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + stage);
|
||||
|
||||
GLenum target = gTextureTargetGL[type];
|
||||
|
||||
if (*cache != this)
|
||||
{
|
||||
*cache = this;
|
||||
|
||||
glBindTexture(target, id);
|
||||
}
|
||||
|
||||
if (cachedState != state)
|
||||
{
|
||||
cachedState = state;
|
||||
|
||||
bool hasMips = mipLevels > 1;
|
||||
|
||||
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, gSamplerFilterMinGL[state.getFilter()][hasMips]);
|
||||
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, gSamplerFilterMagGL[state.getFilter()]);
|
||||
|
||||
glTexParameteri(target, GL_TEXTURE_WRAP_S, gSamplerAddressGL[state.getAddress()]);
|
||||
glTexParameteri(target, GL_TEXTURE_WRAP_T, gSamplerAddressGL[state.getAddress()]);
|
||||
|
||||
#ifndef GLES
|
||||
int anisotropy = state.getFilter() == SamplerState::Filter_Anisotropic ? state.getAnisotropy() : 1;
|
||||
|
||||
glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast<float>(anisotropy));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int TextureGL::getTarget() const
|
||||
{
|
||||
return gTextureTargetGL[type];
|
||||
}
|
||||
|
||||
unsigned int TextureGL::getInternalFormat(Format format)
|
||||
{
|
||||
return gTextureFormatGL[format].internalFormat;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "GfxCore/Texture.h"
|
||||
#include "GfxCore/States.h"
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
namespace Graphics
|
||||
{
|
||||
|
||||
class TextureGL: public Texture, public boost::enable_shared_from_this<TextureGL>
|
||||
{
|
||||
public:
|
||||
TextureGL(Device* device, Type type, Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Usage usage);
|
||||
TextureGL(Device* device, Type type, Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Usage usage, unsigned int id);
|
||||
~TextureGL();
|
||||
|
||||
virtual void upload(unsigned int index, unsigned int mip, const TextureRegion& region, const void* data, unsigned int size);
|
||||
|
||||
virtual bool download(unsigned int index, unsigned int mip, void* data, unsigned int size);
|
||||
|
||||
virtual bool supportsLocking() const;
|
||||
virtual LockResult lock(unsigned int index, unsigned int mip, const TextureRegion& region);
|
||||
virtual void unlock(unsigned int index, unsigned int mip);
|
||||
|
||||
virtual shared_ptr<Renderbuffer> getRenderbuffer(unsigned int index, unsigned int mip);
|
||||
|
||||
virtual void commitChanges();
|
||||
virtual void generateMipmaps();
|
||||
|
||||
void bind(unsigned int stage, const SamplerState& state, TextureGL** cache);
|
||||
|
||||
unsigned int getId() const { return id; }
|
||||
unsigned int getTarget() const;
|
||||
|
||||
static unsigned int getInternalFormat(Format format);
|
||||
|
||||
private:
|
||||
struct Change
|
||||
{
|
||||
unsigned int index;
|
||||
unsigned int mip;
|
||||
TextureRegion region;
|
||||
|
||||
unsigned int scratchOffset;
|
||||
};
|
||||
|
||||
unsigned int id;
|
||||
|
||||
SamplerState cachedState;
|
||||
|
||||
bool external;
|
||||
|
||||
unsigned int scratchId;
|
||||
unsigned int scratchOffset;
|
||||
unsigned int scratchSize;
|
||||
std::vector<Change> pendingChanges;
|
||||
|
||||
typedef std::map<std::pair<unsigned,unsigned>, weak_ptr<Renderbuffer> > RenderBufferMap;
|
||||
RenderBufferMap renderBuffers;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user