mirror of https://github.com/buggins/dlangui.git
1677 lines
57 KiB
D
1677 lines
57 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
This module contains OpenGL access layer.
|
|
|
|
To enable OpenGL support, build with version(USE_OPENGL);
|
|
|
|
Synopsis:
|
|
|
|
----
|
|
import dlangui.graphics.glsupport;
|
|
|
|
----
|
|
|
|
Copyright: Vadim Lopatin, 2014
|
|
License: Boost License 1.0
|
|
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
|
*/
|
|
module dlangui.graphics.glsupport;
|
|
|
|
public import dlangui.core.config;
|
|
static if (BACKEND_GUI):
|
|
static if (ENABLE_OPENGL):
|
|
|
|
public import dlangui.core.math3d;
|
|
|
|
import dlangui.core.logger;
|
|
import dlangui.core.types;
|
|
import dlangui.core.math3d;
|
|
|
|
import std.conv;
|
|
import std.string;
|
|
import std.array;
|
|
|
|
version (Android) {
|
|
enum SUPPORT_LEGACY_OPENGL = false;
|
|
public import EGL.eglplatform : EGLint;
|
|
public import EGL.egl;
|
|
//public import GLES2.gl2;
|
|
public import GLES3.gl3;
|
|
|
|
static if (SUPPORT_LEGACY_OPENGL) {
|
|
public import GLES.gl : glEnableClientState, glLightfv, glColor4f, GL_ALPHA_TEST, GL_VERTEX_ARRAY,
|
|
GL_COLOR_ARRAY, glVertexPointer, glColorPointer, glDisableClientState,
|
|
GL_TEXTURE_COORD_ARRAY, glTexCoordPointer, glColorPointer, glMatrixMode,
|
|
glLoadMatrixf, glLoadIdentity, GL_PROJECTION, GL_MODELVIEW;
|
|
}
|
|
|
|
} else {
|
|
enum SUPPORT_LEGACY_OPENGL = true;
|
|
public import derelict.opengl3.types;
|
|
public import derelict.opengl3.gl3;
|
|
public import derelict.opengl3.gl;
|
|
|
|
derelict.util.exception.ShouldThrow gl3MissingSymFunc( string symName ) {
|
|
import std.algorithm : equal;
|
|
static import derelict.util.exception;
|
|
foreach(s; ["glGetError", "glShaderSource", "glCompileShader",
|
|
"glGetShaderiv", "glGetShaderInfoLog", "glGetString",
|
|
"glCreateProgram", "glUseProgram", "glDeleteProgram",
|
|
"glDeleteShader", "glEnable", "glDisable", "glBlendFunc",
|
|
"glUniformMatrix4fv", "glGetAttribLocation", "glGetUniformLocation",
|
|
"glGenVertexArrays", "glBindVertexArray", "glBufferData",
|
|
"glBindBuffer", "glBufferSubData"]) {
|
|
if (symName.equal(s)) // Symbol is used
|
|
return derelict.util.exception.ShouldThrow.Yes;
|
|
}
|
|
// Don't throw for unused symbol
|
|
return derelict.util.exception.ShouldThrow.No;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
import dlangui.graphics.scene.mesh;
|
|
import dlangui.graphics.scene.effect;
|
|
|
|
|
|
//extern (C) void func(int n);
|
|
//pragma(msg, __traits(identifier, func));
|
|
|
|
/**
|
|
* Convenient wrapper around glGetError()
|
|
* Using: checkgl!glFunction(funcParams);
|
|
* TODO use one of the DEBUG extensions
|
|
*/
|
|
template checkgl(alias func)
|
|
{
|
|
debug auto checkgl(string functionName=__FUNCTION__, int line=__LINE__, Args...)(Args args)
|
|
{
|
|
scope(success) checkError(__traits(identifier, func), functionName, line);
|
|
return func(args);
|
|
} else
|
|
alias checkgl = func;
|
|
}
|
|
bool checkError(string context="", string functionName=__FUNCTION__, int line=__LINE__)
|
|
{
|
|
GLenum err = glGetError();
|
|
if (err != GL_NO_ERROR) {
|
|
Log.e("OpenGL error ", glerrorToString(err), " at ", functionName, ":", line, " -- ", context);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Convenient wrapper around glGetError()
|
|
* Using: checkgl!glFunction(funcParams);
|
|
* TODO use one of the DEBUG extensions
|
|
*/
|
|
template assertgl(alias func)
|
|
{
|
|
auto assertgl(string functionName=__FUNCTION__, int line=__LINE__, Args...)(Args args)
|
|
{
|
|
scope(success) assertNoError(func.stringof, functionName, line);
|
|
return func(args);
|
|
};
|
|
}
|
|
void assertNoError(string context="", string functionName=__FUNCTION__, int line=__LINE__)
|
|
{
|
|
GLenum err = glGetError();
|
|
if (err != GL_NO_ERROR) {
|
|
Log.e("fatal OpenGL error ", glerrorToString(err), " at ", functionName, ":", line, " -- ", context);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
/* For reporting OpenGL errors, it's nicer to get a human-readable symbolic name for the
|
|
* error instead of the numeric form. Derelict's GLenum is just an alias for uint, so we
|
|
* can't depend on D's nice toString() for enums.
|
|
*/
|
|
string glerrorToString(in GLenum err) pure nothrow {
|
|
switch(err) {
|
|
case 0x0500: return "GL_INVALID_ENUM";
|
|
case 0x0501: return "GL_INVALID_VALUE";
|
|
case 0x0502: return "GL_INVALID_OPERATION";
|
|
case 0x0505: return "GL_OUT_OF_MEMORY";
|
|
case 0x0506: return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
|
case 0x0507: return "GL_CONTEXT_LOST";
|
|
case GL_NO_ERROR: return "No GL error";
|
|
default: return "Unknown GL error: " ~ to!string(err);
|
|
}
|
|
}
|
|
|
|
class GLProgram : dlangui.graphics.scene.mesh.GraphicsEffect {
|
|
@property abstract string vertexSource();
|
|
@property abstract string fragmentSource();
|
|
protected GLuint program;
|
|
protected bool initialized;
|
|
protected bool error;
|
|
|
|
private GLuint vertexShader;
|
|
private GLuint fragmentShader;
|
|
private string glslversion;
|
|
private int glslversionInt;
|
|
private char[] glslversionString;
|
|
|
|
private void compatibilityFixes(ref char[] code, GLuint type) {
|
|
if (glslversionInt < 150)
|
|
code = replace(code, " texture(", " texture2D(");
|
|
if (glslversionInt < 140) {
|
|
if(type == GL_VERTEX_SHADER) {
|
|
code = replace(code, "in ", "attribute ");
|
|
code = replace(code, "out ", "varying ");
|
|
} else {
|
|
code = replace(code, "in ", "varying ");
|
|
code = replace(code, "out vec4 outColor;", "");
|
|
code = replace(code, "outColor", "gl_FragColor");
|
|
}
|
|
}
|
|
}
|
|
|
|
private GLuint compileShader(string src, GLuint type) {
|
|
import std.string : toStringz, fromStringz;
|
|
|
|
char[] sourceCode;
|
|
if (glslversionString.length) {
|
|
sourceCode ~= "#version ";
|
|
sourceCode ~= glslversionString;
|
|
sourceCode ~= "\n";
|
|
}
|
|
sourceCode ~= src;
|
|
compatibilityFixes(sourceCode, type);
|
|
|
|
Log.d("compileShader: glslVersion = ", glslversion, ", type: ", (type == GL_VERTEX_SHADER ? "GL_VERTEX_SHADER" : (type == GL_FRAGMENT_SHADER ? "GL_FRAGMENT_SHADER" : "UNKNOWN")));
|
|
//Log.v("Shader code:\n", sourceCode);
|
|
GLuint shader = checkgl!glCreateShader(type);
|
|
const char * psrc = sourceCode.toStringz;
|
|
checkgl!glShaderSource(shader, 1, &psrc, null);
|
|
checkgl!glCompileShader(shader);
|
|
GLint compiled;
|
|
checkgl!glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
|
if (compiled) {
|
|
// compiled successfully
|
|
return shader;
|
|
} else {
|
|
Log.e("Failed to compile shader source:\n", sourceCode);
|
|
GLint blen = 0;
|
|
GLsizei slen = 0;
|
|
checkgl!glGetShaderiv(shader, GL_INFO_LOG_LENGTH , &blen);
|
|
if (blen > 1)
|
|
{
|
|
GLchar[] msg = new GLchar[blen + 1];
|
|
checkgl!glGetShaderInfoLog(shader, blen, &slen, msg.ptr);
|
|
Log.e("Shader compilation error: ", fromStringz(msg.ptr));
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool compile() {
|
|
glslversion = checkgl!fromStringz(cast(const char *)glGetString(GL_SHADING_LANGUAGE_VERSION)).dup;
|
|
glslversionString.length = 0;
|
|
glslversionInt = 0;
|
|
foreach(ch; glslversion) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
glslversionString ~= ch;
|
|
glslversionInt = glslversionInt * 10 + (ch - '0');
|
|
} else if (ch != '.')
|
|
break;
|
|
}
|
|
version (Android) {
|
|
glslversionInt = 130;
|
|
}
|
|
|
|
vertexShader = compileShader(vertexSource, GL_VERTEX_SHADER);
|
|
fragmentShader = compileShader(fragmentSource, GL_FRAGMENT_SHADER);
|
|
if (!vertexShader || !fragmentShader) {
|
|
error = true;
|
|
return false;
|
|
}
|
|
program = checkgl!glCreateProgram();
|
|
checkgl!glAttachShader(program, vertexShader);
|
|
checkgl!glAttachShader(program, fragmentShader);
|
|
checkgl!glLinkProgram(program);
|
|
GLint isLinked = 0;
|
|
checkgl!glGetProgramiv(program, GL_LINK_STATUS, &isLinked);
|
|
if (!isLinked) {
|
|
GLint maxLength = 0;
|
|
checkgl!glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
|
|
GLchar[] msg = new GLchar[maxLength + 1];
|
|
checkgl!glGetProgramInfoLog(program, maxLength, &maxLength, msg.ptr);
|
|
Log.e("Error while linking program: ", fromStringz(msg.ptr));
|
|
error = true;
|
|
return false;
|
|
}
|
|
Log.d("Program linked successfully");
|
|
|
|
initStandardLocations();
|
|
if (!initLocations()) {
|
|
Log.e("some of locations were not found");
|
|
error = true;
|
|
}
|
|
initialized = true;
|
|
Log.v("Program is initialized successfully");
|
|
return !error;
|
|
}
|
|
|
|
|
|
void initStandardLocations() {
|
|
for(DefaultUniform id = DefaultUniform.min; id <= DefaultUniform.max; id++) {
|
|
_uniformIdLocations[id] = getUniformLocation(to!string(id));
|
|
}
|
|
for(DefaultAttribute id = DefaultAttribute.min; id <= DefaultAttribute.max; id++) {
|
|
_attribIdLocations[id] = getAttribLocation(to!string(id));
|
|
}
|
|
}
|
|
|
|
/// override to init shader code locations
|
|
abstract bool initLocations();
|
|
|
|
~this() {
|
|
// TODO: cleanup
|
|
if (program)
|
|
glDeleteProgram(program);
|
|
if (vertexShader)
|
|
glDeleteShader(vertexShader);
|
|
if (fragmentShader)
|
|
glDeleteShader(fragmentShader);
|
|
program = vertexShader = fragmentShader = 0;
|
|
initialized = false;
|
|
}
|
|
|
|
/// returns true if program is ready for use
|
|
bool check() {
|
|
return !error && initialized;
|
|
}
|
|
|
|
static GLuint currentProgram;
|
|
/// binds program to current context
|
|
void bind() {
|
|
if(program != currentProgram) {
|
|
checkgl!glUseProgram(program);
|
|
currentProgram = program;
|
|
}
|
|
}
|
|
|
|
/// unbinds program from current context
|
|
static void unbind() {
|
|
checkgl!glUseProgram(0);
|
|
currentProgram = 0;
|
|
}
|
|
|
|
protected int[string] _uniformLocations;
|
|
protected int[string] _attribLocations;
|
|
protected int[DefaultUniform.max + 1] _uniformIdLocations;
|
|
protected int[DefaultAttribute.max + 1] _attribIdLocations;
|
|
|
|
/// get location for vertex attribute
|
|
override int getVertexElementLocation(VertexElementType type) {
|
|
return VERTEX_ELEMENT_NOT_FOUND;
|
|
}
|
|
|
|
|
|
/// get uniform location from program by uniform id, returns -1 if location is not found
|
|
int getUniformLocation(DefaultUniform uniform) {
|
|
return _uniformIdLocations[uniform];
|
|
}
|
|
|
|
/// get uniform location from program, returns -1 if location is not found
|
|
int getUniformLocation(string variableName) {
|
|
if (auto p = variableName in _uniformLocations)
|
|
return *p;
|
|
int res = checkgl!glGetUniformLocation(program, variableName.toStringz);
|
|
//if (res == -1)
|
|
// Log.e("glGetUniformLocation failed for " ~ variableName);
|
|
_uniformLocations[variableName] = res;
|
|
return res;
|
|
}
|
|
|
|
/// get attribute location from program by uniform id, returns -1 if location is not found
|
|
int getAttribLocation(DefaultAttribute id) {
|
|
return _attribIdLocations[id];
|
|
}
|
|
|
|
/// get attribute location from program, returns -1 if location is not found
|
|
int getAttribLocation(string variableName) {
|
|
if (auto p = variableName in _attribLocations)
|
|
return *p;
|
|
int res = checkgl!glGetAttribLocation(program, variableName.toStringz);
|
|
//if (res == -1)
|
|
// Log.e("glGetAttribLocation failed for " ~ variableName);
|
|
_attribLocations[variableName] = res;
|
|
return res;
|
|
}
|
|
|
|
override void setUniform(string uniformName, const vec2[] vec) {
|
|
checkgl!glUniform2fv(getUniformLocation(uniformName), cast(int)vec.length, cast(const(float)*)vec.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, const vec2[] vec) {
|
|
checkgl!glUniform2fv(getUniformLocation(id), cast(int)vec.length, cast(const(float)*)vec.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, vec2 vec) {
|
|
checkgl!glUniform2fv(getUniformLocation(uniformName), 1, vec.vec.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, vec2 vec) {
|
|
checkgl!glUniform2fv(getUniformLocation(id), 1, vec.vec.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, vec3 vec) {
|
|
checkgl!glUniform3fv(getUniformLocation(uniformName), 1, vec.vec.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, vec3 vec) {
|
|
checkgl!glUniform3fv(getUniformLocation(id), 1, vec.vec.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, const vec3[] vec) {
|
|
checkgl!glUniform3fv(getUniformLocation(uniformName), cast(int)vec.length, cast(const(float)*)vec.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, const vec3[] vec) {
|
|
checkgl!glUniform3fv(getUniformLocation(id), cast(int)vec.length, cast(const(float)*)vec.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, vec4 vec) {
|
|
checkgl!glUniform4fv(getUniformLocation(uniformName), 1, vec.vec.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, vec4 vec) {
|
|
checkgl!glUniform4fv(getUniformLocation(id), 1, vec.vec.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, const vec4[] vec) {
|
|
checkgl!glUniform4fv(getUniformLocation(uniformName), cast(int)vec.length, cast(const(float)*)vec.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, const vec4[] vec) {
|
|
checkgl!glUniform4fv(getUniformLocation(id), cast(int)vec.length, cast(const(float)*)vec.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, ref const(mat4) matrix) {
|
|
checkgl!glUniformMatrix4fv(getUniformLocation(uniformName), 1, false, matrix.m.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, ref const(mat4) matrix) {
|
|
checkgl!glUniformMatrix4fv(getUniformLocation(id), 1, false, matrix.m.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, const(mat4)[] matrix) {
|
|
checkgl!glUniformMatrix4fv(getUniformLocation(uniformName), cast(int)matrix.length, false, cast(const(float)*)matrix.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, const(mat4)[] matrix) {
|
|
checkgl!glUniformMatrix4fv(getUniformLocation(id), cast(int)matrix.length, false, cast(const(float)*)matrix.ptr);
|
|
}
|
|
|
|
override void setUniform(string uniformName, float v) {
|
|
checkgl!glUniform1f(getUniformLocation(uniformName), v);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, float v) {
|
|
checkgl!glUniform1f(getUniformLocation(id), v);
|
|
}
|
|
|
|
override void setUniform(string uniformName, const float[] v) {
|
|
checkgl!glUniform1fv(getUniformLocation(uniformName), cast(int)v.length, cast(const(float)*)v.ptr);
|
|
}
|
|
|
|
override void setUniform(DefaultUniform id, const float[] v) {
|
|
checkgl!glUniform1fv(getUniformLocation(id), cast(int)v.length, cast(const(float)*)v.ptr);
|
|
}
|
|
|
|
/// returns true if effect has uniform
|
|
override bool hasUniform(DefaultUniform id) {
|
|
return getUniformLocation(id) >= 0;
|
|
}
|
|
|
|
/// returns true if effect has uniform
|
|
override bool hasUniform(string uniformName) {
|
|
return getUniformLocation(uniformName) >= 0;
|
|
}
|
|
|
|
/// draw mesh using this program (program should be bound by this time and all uniforms should be set)
|
|
override void draw(Mesh mesh, bool wireframe) {
|
|
VertexBuffer vb = mesh.vertexBuffer;
|
|
if (!vb) {
|
|
vb = new GLVertexBuffer();
|
|
mesh.vertexBuffer = vb;
|
|
}
|
|
vb.draw(this, wireframe);
|
|
}
|
|
}
|
|
|
|
class SolidFillProgram : GLProgram {
|
|
@property override string vertexSource() {
|
|
return q{
|
|
in vec3 a_position;
|
|
in vec4 a_color;
|
|
out vec4 col;
|
|
uniform mat4 u_worldViewProjectionMatrix;
|
|
void main(void)
|
|
{
|
|
gl_Position = u_worldViewProjectionMatrix * vec4(a_position, 1);
|
|
col = a_color;
|
|
}
|
|
};
|
|
}
|
|
|
|
@property override string fragmentSource() {
|
|
return q{
|
|
in vec4 col;
|
|
out vec4 outColor;
|
|
void main(void)
|
|
{
|
|
outColor = col;
|
|
}
|
|
};
|
|
}
|
|
|
|
protected GLint matrixLocation;
|
|
protected GLint vertexLocation;
|
|
protected GLint colAttrLocation;
|
|
override bool initLocations() {
|
|
matrixLocation = getUniformLocation(DefaultUniform.u_worldViewProjectionMatrix);
|
|
vertexLocation = getAttribLocation(DefaultAttribute.a_position);
|
|
colAttrLocation = getAttribLocation(DefaultAttribute.a_color);
|
|
return matrixLocation >= 0 && vertexLocation >= 0 && colAttrLocation >= 0;
|
|
}
|
|
/// get location for vertex attribute
|
|
override int getVertexElementLocation(VertexElementType type) {
|
|
switch(type) with(VertexElementType) {
|
|
case POSITION:
|
|
return vertexLocation;
|
|
case COLOR:
|
|
return colAttrLocation;
|
|
default:
|
|
return VERTEX_ELEMENT_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
VAO vao;
|
|
|
|
protected void beforeExecute() {
|
|
bind();
|
|
setUniform(DefaultUniform.u_worldViewProjectionMatrix, glSupport.projectionMatrix);
|
|
}
|
|
|
|
protected void createVAO(size_t verticesBufferLength) {
|
|
vao = new VAO;
|
|
|
|
glVertexAttribPointer(vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, cast(void*) 0);
|
|
glVertexAttribPointer(colAttrLocation, 4, GL_FLOAT, GL_FALSE, 0, cast(void*) (verticesBufferLength * float.sizeof));
|
|
|
|
glEnableVertexAttribArray(vertexLocation);
|
|
glEnableVertexAttribArray(colAttrLocation);
|
|
}
|
|
|
|
bool drawBatch(int length, int start, bool areLines = false) {
|
|
if(!check())
|
|
return false;
|
|
beforeExecute();
|
|
|
|
vao.bind();
|
|
|
|
checkgl!glDrawElements(areLines ? GL_LINES : GL_TRIANGLES, cast(int)length, GL_UNSIGNED_INT, cast(void*)(start * 4));
|
|
|
|
return true;
|
|
}
|
|
|
|
void destroyBuffers() {
|
|
destroy(vao);
|
|
vao = null;
|
|
}
|
|
}
|
|
|
|
class TextureProgram : SolidFillProgram {
|
|
@property override string vertexSource() {
|
|
return q{
|
|
in vec3 a_position;
|
|
in vec4 a_color;
|
|
in vec2 a_texCoord;
|
|
out vec4 col;
|
|
out vec2 UV;
|
|
uniform mat4 u_worldViewProjectionMatrix;
|
|
void main(void)
|
|
{
|
|
gl_Position = u_worldViewProjectionMatrix * vec4(a_position, 1);
|
|
col = a_color;
|
|
UV = a_texCoord;
|
|
}
|
|
};
|
|
}
|
|
@property override string fragmentSource() {
|
|
return q{
|
|
uniform sampler2D tex;
|
|
in vec4 col;
|
|
in vec2 UV;
|
|
out vec4 outColor;
|
|
void main(void)
|
|
{
|
|
outColor = texture(tex, UV) * col;
|
|
}
|
|
};
|
|
}
|
|
|
|
GLint texCoordLocation;
|
|
override bool initLocations() {
|
|
bool res = super.initLocations();
|
|
texCoordLocation = getAttribLocation(DefaultAttribute.a_texCoord);
|
|
return res && texCoordLocation >= 0;
|
|
}
|
|
/// get location for vertex attribute
|
|
override int getVertexElementLocation(VertexElementType type) {
|
|
switch(type) with(VertexElementType) {
|
|
case TEXCOORD0:
|
|
return texCoordLocation;
|
|
default:
|
|
return super.getVertexElementLocation(type);
|
|
}
|
|
}
|
|
|
|
protected void createVAO(size_t verticesBufferLength, size_t colorsBufferLength) {
|
|
vao = new VAO;
|
|
|
|
glVertexAttribPointer(vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, cast(void*) 0);
|
|
glVertexAttribPointer(colAttrLocation, 4, GL_FLOAT, GL_FALSE, 0, cast(void*) (verticesBufferLength * float.sizeof));
|
|
glVertexAttribPointer(texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, cast(void*) ((verticesBufferLength + colorsBufferLength) * float.sizeof));
|
|
|
|
glEnableVertexAttribArray(vertexLocation);
|
|
glEnableVertexAttribArray(colAttrLocation);
|
|
glEnableVertexAttribArray(texCoordLocation);
|
|
}
|
|
|
|
bool drawBatch(Tex2D texture, bool linear, int length, int start) {
|
|
if(!check())
|
|
return false;
|
|
beforeExecute();
|
|
|
|
texture.setup();
|
|
texture.setSamplerParams(linear);
|
|
|
|
vao.bind();
|
|
|
|
checkgl!glDrawElements(GL_TRIANGLES, cast(int)length, GL_UNSIGNED_INT, cast(void*)(start * 4));
|
|
|
|
texture.unbind();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
struct Color
|
|
{
|
|
float r, g, b, a;
|
|
}
|
|
|
|
// utility function to fill 4-float array of vertex colors with converted CR 32bit color
|
|
private void FillColor(uint color, Color[] buf_slice) {
|
|
float r = ((color >> 16) & 255) / 255.0;
|
|
float g = ((color >> 8) & 255) / 255.0;
|
|
float b = ((color >> 0) & 255) / 255.0;
|
|
float a = (((color >> 24) & 255) ^ 255) / 255.0;
|
|
foreach(ref col; buf_slice) {
|
|
col.r = r;
|
|
col.g = g;
|
|
col.b = b;
|
|
col.a = a;
|
|
}
|
|
}
|
|
|
|
|
|
import std.functional;
|
|
alias convertColors = memoize!(convertColorsImpl);
|
|
|
|
float[] convertColorsImpl(uint[] cols) pure nothrow {
|
|
float[] colors;
|
|
colors.length = cols.length * 4;
|
|
foreach(i; 0 .. cols.length) {
|
|
uint color = cols[i];
|
|
float r = ((color >> 16) & 255) / 255.0;
|
|
float g = ((color >> 8) & 255) / 255.0;
|
|
float b = ((color >> 0) & 255) / 255.0;
|
|
float a = (((color >> 24) & 255) ^ 255) / 255.0;
|
|
colors[i * 4 + 0] = r;
|
|
colors[i * 4 + 1] = g;
|
|
colors[i * 4 + 2] = b;
|
|
colors[i * 4 + 3] = a;
|
|
}
|
|
return colors;
|
|
}
|
|
|
|
private __gshared GLSupport _glSupport;
|
|
@property GLSupport glSupport() {
|
|
if (!_glSupport) {
|
|
Log.f("GLSupport is not initialized");
|
|
assert(false, "GLSupport is not initialized");
|
|
}
|
|
if (!_glSupport.valid) {
|
|
Log.e("GLSupport programs are not initialized");
|
|
}
|
|
return _glSupport;
|
|
}
|
|
|
|
__gshared bool glNoContext;
|
|
|
|
/// initialize OpenGL support helper (call when current OpenGL context is initialized)
|
|
bool initGLSupport(bool legacy = false) {
|
|
import dlangui.platforms.common.platform : setOpenglEnabled;
|
|
if (_glSupport && _glSupport.valid)
|
|
return true;
|
|
version(Android) {
|
|
Log.d("initGLSupport");
|
|
} else {
|
|
static bool DERELICT_GL3_RELOADED;
|
|
static bool gl3ReloadedOk;
|
|
static bool glReloadedOk;
|
|
if (!DERELICT_GL3_RELOADED) {
|
|
DERELICT_GL3_RELOADED = true;
|
|
try {
|
|
Log.v("Reloading DerelictGL3");
|
|
import derelict.opengl3.gl3;
|
|
DerelictGL3.missingSymbolCallback = &gl3MissingSymFunc;
|
|
DerelictGL3.reload();
|
|
gl3ReloadedOk = true;
|
|
} catch (Exception e) {
|
|
Log.e("Derelict exception while reloading DerelictGL3", e);
|
|
}
|
|
try {
|
|
Log.v("Reloading DerelictGL");
|
|
import derelict.opengl3.gl;
|
|
DerelictGL.missingSymbolCallback = &gl3MissingSymFunc;
|
|
DerelictGL.reload();
|
|
glReloadedOk = true;
|
|
} catch (Exception e) {
|
|
Log.e("Derelict exception while reloading DerelictGL", e);
|
|
}
|
|
}
|
|
if (!gl3ReloadedOk && !glReloadedOk) {
|
|
Log.e("Neither DerelictGL3 nor DerelictGL were reloaded successfully");
|
|
return false;
|
|
}
|
|
if (!gl3ReloadedOk)
|
|
legacy = true;
|
|
else if (!glReloadedOk)
|
|
legacy = false;
|
|
}
|
|
if (!_glSupport) {
|
|
Log.d("glSupport not initialized: trying to create");
|
|
int major = *cast(int*)(glGetString(GL_VERSION)[0 .. 1].ptr);
|
|
legacy = legacy || (major < 3);
|
|
_glSupport = new GLSupport(legacy);
|
|
if (!_glSupport.valid) {
|
|
Log.e("Failed to compile shaders");
|
|
version (Android) {
|
|
// do not recreate legacy mode
|
|
} else {
|
|
// try opposite legacy flag
|
|
if (_glSupport.legacyMode == legacy) {
|
|
Log.i("Trying to reinit GLSupport with legacy flag ", !legacy);
|
|
_glSupport = new GLSupport(!legacy);
|
|
}
|
|
// Situation when opposite legacy flag is true and GL version is 3+ with no old functions
|
|
if (_glSupport.legacyMode) {
|
|
if (major >= 3) {
|
|
Log.e("Try to create OpenGL context with <= 3.1 version");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (_glSupport.valid) {
|
|
setOpenglEnabled();
|
|
Log.v("OpenGL is initialized ok");
|
|
return true;
|
|
} else {
|
|
Log.e("Failed to compile shaders");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// OpenGL support helper
|
|
final class GLSupport {
|
|
|
|
private bool _legacyMode;
|
|
@property bool legacyMode() { return _legacyMode; }
|
|
@property queue() { return _queue; }
|
|
|
|
@property bool valid() {
|
|
return _legacyMode || _shadersAreInitialized;
|
|
}
|
|
|
|
this(bool legacy = false) {
|
|
_queue = new OpenGLQueue;
|
|
version (Android) {
|
|
Log.d("creating GLSupport");
|
|
} else {
|
|
if (legacy && !glLightfv) {
|
|
Log.w("GLSupport legacy API is not supported");
|
|
legacy = false;
|
|
}
|
|
}
|
|
_legacyMode = legacy;
|
|
if (!_legacyMode)
|
|
_shadersAreInitialized = initShaders();
|
|
}
|
|
|
|
~this() {
|
|
uninitShaders();
|
|
}
|
|
|
|
private OpenGLQueue _queue;
|
|
|
|
private SolidFillProgram _solidFillProgram;
|
|
private TextureProgram _textureProgram;
|
|
|
|
private bool _shadersAreInitialized;
|
|
private bool initShaders() {
|
|
if (_solidFillProgram is null) {
|
|
Log.v("Compiling solid fill program");
|
|
_solidFillProgram = new SolidFillProgram();
|
|
_solidFillProgram.compile();
|
|
if (!_solidFillProgram.check())
|
|
return false;
|
|
}
|
|
if (_textureProgram is null) {
|
|
Log.v("Compiling texture program");
|
|
_textureProgram = new TextureProgram();
|
|
_textureProgram.compile();
|
|
if (!_textureProgram.check())
|
|
return false;
|
|
}
|
|
Log.d("Shaders compiled successfully");
|
|
return true;
|
|
}
|
|
|
|
private void uninitShaders() {
|
|
Log.d("Uniniting shaders");
|
|
if (_solidFillProgram !is null) {
|
|
destroy(_solidFillProgram);
|
|
_solidFillProgram = null;
|
|
}
|
|
if (_textureProgram !is null) {
|
|
destroy(_textureProgram);
|
|
_textureProgram = null;
|
|
}
|
|
}
|
|
|
|
void beforeRenderGUI() {
|
|
glEnable(GL_BLEND);
|
|
checkgl!glDisable(GL_CULL_FACE);
|
|
checkgl!glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
private VBO vbo;
|
|
private EBO ebo;
|
|
|
|
private void fillBuffers(float[] vertices, float[] colors, float[] texcoords, int[] indices) {
|
|
resetBindings();
|
|
|
|
if(_legacyMode)
|
|
return;
|
|
|
|
vbo = new VBO;
|
|
ebo = new EBO;
|
|
|
|
vbo.bind();
|
|
vbo.fill([vertices, colors, texcoords]);
|
|
|
|
ebo.bind();
|
|
ebo.fill(indices);
|
|
|
|
// create vertex array objects and bind vertex buffers to them
|
|
_solidFillProgram.createVAO(vertices.length);
|
|
vbo.bind();
|
|
ebo.bind();
|
|
_textureProgram.createVAO(vertices.length, colors.length);
|
|
vbo.bind();
|
|
ebo.bind();
|
|
}
|
|
|
|
/// This function is needed to draw custom OpenGL scene correctly (especially on legacy API)
|
|
private void resetBindings() {
|
|
import std.traits : isFunction;
|
|
if (isFunction!glUseProgram)
|
|
GLProgram.unbind();
|
|
if (isFunction!glBindVertexArray)
|
|
VAO.unbind();
|
|
if (isFunction!glBindBuffer)
|
|
VBO.unbind();
|
|
}
|
|
|
|
private void destroyBuffers() {
|
|
resetBindings();
|
|
|
|
if(_legacyMode)
|
|
return;
|
|
|
|
if (_solidFillProgram)
|
|
_solidFillProgram.destroyBuffers();
|
|
if (_textureProgram)
|
|
_textureProgram.destroyBuffers();
|
|
|
|
destroy(vbo);
|
|
destroy(ebo);
|
|
vbo = null;
|
|
ebo = null;
|
|
}
|
|
|
|
private void drawLines(int length, int start) {
|
|
if (_legacyMode) {
|
|
static if (SUPPORT_LEGACY_OPENGL) {
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
glVertexPointer(3, GL_FLOAT, 0, cast(void*)_queue._vertices.ptr);
|
|
glColorPointer(4, GL_FLOAT, 0, cast(void*)_queue._colors.ptr);
|
|
|
|
checkgl!glDrawElements(GL_LINES, cast(int)length, GL_UNSIGNED_INT, cast(void*)(_queue._indices[start .. start + length].ptr));
|
|
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
}
|
|
} else {
|
|
if (_solidFillProgram !is null) {
|
|
_solidFillProgram.drawBatch(length, start, true);
|
|
} else
|
|
Log.e("No program");
|
|
}
|
|
}
|
|
|
|
private void drawSolidFillTriangles(int length, int start) {
|
|
if (_legacyMode) {
|
|
static if (SUPPORT_LEGACY_OPENGL) {
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
glVertexPointer(3, GL_FLOAT, 0, cast(void*)_queue._vertices.ptr);
|
|
glColorPointer(4, GL_FLOAT, 0, cast(void*)_queue._colors.ptr);
|
|
|
|
checkgl!glDrawElements(GL_TRIANGLES, cast(int)length, GL_UNSIGNED_INT, cast(void*)(_queue._indices[start .. start + length].ptr));
|
|
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
}
|
|
} else {
|
|
if (_solidFillProgram !is null) {
|
|
_solidFillProgram.drawBatch(length, start);
|
|
} else
|
|
Log.e("No program");
|
|
}
|
|
}
|
|
|
|
private void drawColorAndTextureTriangles(Tex2D texture, bool linear, int length, int start) {
|
|
if (_legacyMode) {
|
|
static if (SUPPORT_LEGACY_OPENGL) {
|
|
glEnable(GL_TEXTURE_2D);
|
|
texture.setup();
|
|
texture.setSamplerParams(linear);
|
|
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glVertexPointer(3, GL_FLOAT, 0, cast(void*)_queue._vertices.ptr);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, cast(void*)_queue._texCoords.ptr);
|
|
glColorPointer(4, GL_FLOAT, 0, cast(void*)_queue._colors.ptr);
|
|
|
|
checkgl!glDrawElements(GL_TRIANGLES, cast(int)length, GL_UNSIGNED_INT, cast(void*)(_queue._indices[start .. start + length].ptr));
|
|
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
} else {
|
|
_textureProgram.drawBatch(texture, linear, length, start);
|
|
}
|
|
}
|
|
|
|
/// call glFlush
|
|
void flushGL() {
|
|
checkgl!glFlush();
|
|
}
|
|
|
|
bool generateMipmap(int dx, int dy, ubyte * pixels, int level, ref ubyte[] dst) {
|
|
if ((dx & 1) || (dy & 1) || dx < 2 || dy < 2)
|
|
return false; // size is not even
|
|
int newdx = dx / 2;
|
|
int newdy = dy / 2;
|
|
int newlen = newdx * newdy * 4;
|
|
if (newlen > dst.length)
|
|
dst.length = newlen;
|
|
ubyte * dstptr = dst.ptr;
|
|
ubyte * srcptr = pixels;
|
|
int srcstride = dx * 4;
|
|
for (int y = 0; y < newdy; y++) {
|
|
for (int x = 0; x < newdx; x++) {
|
|
dstptr[0] = cast(ubyte)((srcptr[0+0] + srcptr[0+4] + srcptr[0+srcstride] + srcptr[0+srcstride + 4])>>2);
|
|
dstptr[1] = cast(ubyte)((srcptr[1+0] + srcptr[1+4] + srcptr[1+srcstride] + srcptr[1+srcstride + 4])>>2);
|
|
dstptr[2] = cast(ubyte)((srcptr[2+0] + srcptr[2+4] + srcptr[2+srcstride] + srcptr[2+srcstride + 4])>>2);
|
|
dstptr[3] = cast(ubyte)((srcptr[3+0] + srcptr[3+4] + srcptr[3+srcstride] + srcptr[3+srcstride + 4])>>2);
|
|
dstptr += 4;
|
|
srcptr += 8;
|
|
}
|
|
srcptr += srcstride; // skip srcline
|
|
}
|
|
checkgl!glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, newdx, newdy, 0, GL_RGBA, GL_UNSIGNED_BYTE, dst.ptr);
|
|
return true;
|
|
}
|
|
|
|
bool setTextureImage(Tex2D texture, int dx, int dy, ubyte * pixels, int mipmapLevels = 0) {
|
|
checkError("before setTextureImage");
|
|
texture.bind();
|
|
checkgl!glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
texture.setSamplerParams(true, true);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapLevels > 0 ? mipmapLevels - 1 : 0);
|
|
// ORIGINAL: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dx, dy, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
checkgl!glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dx, dy, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
if (checkError("updateTexture - glTexImage2D")) {
|
|
Log.e("Cannot set image for texture");
|
|
return false;
|
|
}
|
|
if (mipmapLevels > 1) {
|
|
ubyte[] buffer;
|
|
ubyte * src = pixels;
|
|
int ndx = dx;
|
|
int ndy = dy;
|
|
for (int i = 1; i < mipmapLevels; i++) {
|
|
if (!generateMipmap(ndx, ndy, src, i, buffer))
|
|
break;
|
|
ndx /= 2;
|
|
ndy /= 2;
|
|
src = buffer.ptr;
|
|
}
|
|
}
|
|
texture.unbind();
|
|
return true;
|
|
}
|
|
|
|
bool setTextureImageAlpha(Tex2D texture, int dx, int dy, ubyte * pixels) {
|
|
checkError("before setTextureImageAlpha");
|
|
texture.bind();
|
|
checkgl!glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
texture.setSamplerParams(true, true);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, dx, dy, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels);
|
|
if (checkError("setTextureImageAlpha - glTexImage2D")) {
|
|
Log.e("Cannot set image for texture");
|
|
return false;
|
|
}
|
|
texture.unbind();
|
|
return true;
|
|
}
|
|
|
|
private FBO currentFBO;
|
|
|
|
/// returns texture for buffer, null if failed
|
|
bool createFramebuffer(out Tex2D texture, out FBO fbo, int dx, int dy) {
|
|
checkError("before createFramebuffer");
|
|
bool res = true;
|
|
texture = new Tex2D();
|
|
if (!texture.ID)
|
|
return false;
|
|
checkError("glBindTexture GL_TEXTURE_2D");
|
|
FBO f = new FBO();
|
|
if (!f.ID)
|
|
return false;
|
|
fbo = f;
|
|
|
|
checkgl!glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dx, dy, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, null);
|
|
|
|
texture.setSamplerParams(true, true);
|
|
|
|
checkgl!glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.ID, 0);
|
|
// Always check that our framebuffer is ok
|
|
if(checkgl!glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
|
Log.e("glFramebufferTexture2D failed");
|
|
res = false;
|
|
}
|
|
checkgl!glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
|
|
checkgl!glClear(GL_COLOR_BUFFER_BIT);
|
|
currentFBO = fbo;
|
|
|
|
texture.unbind();
|
|
fbo.unbind();
|
|
|
|
return res;
|
|
}
|
|
|
|
void deleteFramebuffer(ref FBO fbo) {
|
|
if (fbo.ID != 0) {
|
|
destroy(fbo);
|
|
}
|
|
currentFBO = null;
|
|
}
|
|
|
|
bool bindFramebuffer(FBO fbo) {
|
|
fbo.bind();
|
|
currentFBO = fbo;
|
|
return !checkError("glBindFramebuffer");
|
|
}
|
|
|
|
void clearDepthBuffer() {
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
//glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
}
|
|
|
|
/// projection matrix
|
|
/// current gl buffer width
|
|
private int bufferDx;
|
|
/// current gl buffer height
|
|
private int bufferDy;
|
|
private mat4 _projectionMatrix;
|
|
|
|
@property ref mat4 projectionMatrix() {
|
|
return _projectionMatrix;
|
|
}
|
|
|
|
void setOrthoProjection(Rect windowRect, Rect view) {
|
|
flushGL();
|
|
bufferDx = windowRect.width;
|
|
bufferDy = windowRect.height;
|
|
_projectionMatrix.setOrtho(view.left, view.right, view.top, view.bottom, 0.5f, 50.0f);
|
|
|
|
if (_legacyMode) {
|
|
static if (SUPPORT_LEGACY_OPENGL) {
|
|
glMatrixMode(GL_PROJECTION);
|
|
//checkgl!glPushMatrix();
|
|
//glLoadIdentity();
|
|
glLoadMatrixf(_projectionMatrix.m.ptr);
|
|
//glOrthof(0, _dx, 0, _dy, -1.0f, 1.0f);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
//checkgl!glPushMatrix();
|
|
glLoadIdentity();
|
|
}
|
|
}
|
|
checkgl!glViewport(view.left, currentFBO ? view.top : windowRect.height - view.bottom, view.width, view.height);
|
|
}
|
|
|
|
void setPerspectiveProjection(Rect windowRect, Rect view, float fieldOfView, float nearPlane, float farPlane) {
|
|
flushGL();
|
|
bufferDx = windowRect.width;
|
|
bufferDy = windowRect.height;
|
|
float aspectRatio = cast(float)view.width / cast(float)view.height;
|
|
_projectionMatrix.setPerspective(fieldOfView, aspectRatio, nearPlane, farPlane);
|
|
if (_legacyMode) {
|
|
static if (SUPPORT_LEGACY_OPENGL) {
|
|
glMatrixMode(GL_PROJECTION);
|
|
//checkgl!glPushMatrix();
|
|
//glLoadIdentity();
|
|
glLoadMatrixf(_projectionMatrix.m.ptr);
|
|
//glOrthof(0, _dx, 0, _dy, -1.0f, 1.0f);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
//checkgl!glPushMatrix();
|
|
glLoadIdentity();
|
|
}
|
|
}
|
|
checkgl!glViewport(view.left, currentFBO ? view.top : windowRect.height - view.bottom, view.width, view.height);
|
|
}
|
|
}
|
|
|
|
enum GLObjectTypes { Buffer, VertexArray, Texture, Framebuffer };
|
|
/** RAII OpenGL object template.
|
|
* Note: on construction it binds itself to the target, and it binds 0 to target on destruction.
|
|
* All methods (except ctor, dtor, bind(), unbind() and setup()) does not perform binding.
|
|
*/
|
|
|
|
|
|
class GLObject(GLObjectTypes type, GLuint target = 0) {
|
|
immutable GLuint ID;
|
|
//alias ID this; // good, but it confuses destroy()
|
|
|
|
this() {
|
|
GLuint handle;
|
|
mixin("checkgl!glGen" ~ to!string(type) ~ "s(1, &handle);");
|
|
ID = handle;
|
|
bind();
|
|
}
|
|
|
|
~this() {
|
|
if (!glNoContext) {
|
|
unbind();
|
|
mixin("checkgl!glDelete" ~ to!string(type) ~ "s(1, &ID);");
|
|
}
|
|
}
|
|
|
|
void bind() {
|
|
static if(target != 0)
|
|
mixin("glBind" ~ to!string(type) ~ "(" ~ to!string(target) ~ ", ID);");
|
|
else
|
|
mixin("glBind" ~ to!string(type) ~ "(ID);");
|
|
}
|
|
|
|
static void unbind() {
|
|
static if(target != 0)
|
|
mixin("checkgl!glBind" ~ to!string(type) ~ "(" ~ to!string(target) ~ ", 0);");
|
|
else
|
|
mixin("checkgl!glBind" ~ to!string(type) ~ "(0);");
|
|
}
|
|
|
|
static if(type == GLObjectTypes.Buffer)
|
|
{
|
|
void fill(float[][] buffs) {
|
|
int length;
|
|
foreach(b; buffs)
|
|
length += b.length;
|
|
checkgl!glBufferData(target,
|
|
length * float.sizeof,
|
|
null,
|
|
GL_STATIC_DRAW);
|
|
int offset;
|
|
foreach(b; buffs) {
|
|
checkgl!glBufferSubData(target,
|
|
offset,
|
|
b.length * float.sizeof,
|
|
b.ptr);
|
|
offset += b.length * float.sizeof;
|
|
}
|
|
}
|
|
|
|
static if (target == GL_ELEMENT_ARRAY_BUFFER) {
|
|
void fill(int[] indexes) {
|
|
checkgl!glBufferData(target, cast(int)(indexes.length * int.sizeof), indexes.ptr, GL_STATIC_DRAW);
|
|
}
|
|
}
|
|
}
|
|
|
|
static if(type == GLObjectTypes.Texture)
|
|
{
|
|
void setSamplerParams(bool linear, bool clamp = false, bool mipmap = false) {
|
|
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
|
|
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, linear ?
|
|
(!mipmap ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR) :
|
|
(!mipmap ? GL_NEAREST : GL_NEAREST_MIPMAP_NEAREST)); //GL_NEAREST_MIPMAP_NEAREST
|
|
checkError("filtering - glTexParameteri");
|
|
if(clamp) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
checkError("clamp - glTexParameteri");
|
|
}
|
|
}
|
|
|
|
void setup(GLuint binding = 0) {
|
|
glActiveTexture(GL_TEXTURE0 + binding);
|
|
glBindTexture(target, ID);
|
|
checkError("setup texture");
|
|
}
|
|
}
|
|
}
|
|
|
|
alias VAO = GLObject!(GLObjectTypes.VertexArray);
|
|
alias VBO = GLObject!(GLObjectTypes.Buffer, GL_ARRAY_BUFFER);
|
|
alias EBO = GLObject!(GLObjectTypes.Buffer, GL_ELEMENT_ARRAY_BUFFER);
|
|
alias Tex2D = GLObject!(GLObjectTypes.Texture, GL_TEXTURE_2D);
|
|
alias FBO = GLObject!(GLObjectTypes.Framebuffer, GL_FRAMEBUFFER);
|
|
|
|
class GLVertexBuffer : VertexBuffer {
|
|
protected VertexFormat _format;
|
|
protected IndexFragment[] _indexFragments;
|
|
protected int _vertexCount;
|
|
protected GLuint _vertexBuffer;
|
|
protected GLuint _indexBuffer;
|
|
protected GLuint _vao;
|
|
|
|
this() {
|
|
version (Android) {
|
|
checkgl!glGenBuffers(1, &_vertexBuffer);
|
|
checkgl!glGenBuffers(1, &_indexBuffer);
|
|
checkgl!glGenVertexArrays(1, &_vao);
|
|
} else {
|
|
assertgl!glGenBuffers(1, &_vertexBuffer);
|
|
assertgl!glGenBuffers(1, &_indexBuffer);
|
|
assertgl!glGenVertexArrays(1, &_vao);
|
|
}
|
|
}
|
|
|
|
~this() {
|
|
checkgl!glDeleteBuffers(1, &_vertexBuffer);
|
|
checkgl!glDeleteBuffers(1, &_indexBuffer);
|
|
checkgl!glDeleteVertexArrays(1, &_vao);
|
|
}
|
|
|
|
///// bind into current context
|
|
//override void bind() {
|
|
// checkgl!glBindVertexArray(_vao);
|
|
//
|
|
// // TODO: is it necessary to bind vertex/index buffers?
|
|
// // specify vertex buffer
|
|
// checkgl!glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
|
|
// // specify index buffer
|
|
// checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
|
|
//}
|
|
//
|
|
///// unbind from current context
|
|
//override void unbind() {
|
|
// checkgl!glBindVertexArray(0);
|
|
// checkgl!glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
// checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
//}
|
|
|
|
/// update vertex element locations for effect/shader program
|
|
void enableAttributes(GraphicsEffect effect) {
|
|
checkgl!glBindVertexArray(_vao);
|
|
// specify vertex buffer
|
|
checkgl!glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
|
|
// specify index buffer
|
|
checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
|
|
int offset = 0;
|
|
//Log.v("=== enableAttributes for ", _format);
|
|
for(int i = 0; i < _format.length; i++) {
|
|
int loc = effect.getVertexElementLocation(_format[i].type);
|
|
if (loc >= 0) {
|
|
//Log.v("setting attrib pointer for type ", _format[i].type, " offset=", offset, " location=", loc);
|
|
checkgl!glVertexAttribPointer(loc, _format[i].size, GL_FLOAT, cast(ubyte)GL_FALSE, _format.vertexSize, cast(char*)(offset));
|
|
checkgl!glEnableVertexAttribArray(loc);
|
|
} else {
|
|
//Log.v("Attribute location not found for ", _format[i].type);
|
|
}
|
|
offset += _format[i].byteSize;
|
|
}
|
|
}
|
|
|
|
void disableAttributes(GraphicsEffect effect) {
|
|
for(int i = 0; i < _format.length; i++) {
|
|
int loc = effect.getVertexElementLocation(_format[i].type);
|
|
if (loc >= 0) {
|
|
checkgl!glDisableVertexAttribArray(loc);
|
|
}
|
|
}
|
|
checkgl!glBindVertexArray(0);
|
|
checkgl!glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
//unbind();
|
|
}
|
|
|
|
/// set or change data
|
|
override void setData(Mesh mesh) {
|
|
_format = mesh.vertexFormat;
|
|
_indexFragments = mesh.indexFragments;
|
|
_vertexCount = mesh.vertexCount;
|
|
const(ushort[]) indexData = mesh.indexData;
|
|
|
|
Log.d("GLVertexBuffer.setData vertex data size=", mesh.vertexData.length, " index data size=", indexData.length, " vertex count=", _vertexCount, " indexBuffer=", _indexBuffer, " vertexBuffer=", _vertexBuffer, " vao=", _vao);
|
|
|
|
// vertex buffer
|
|
checkgl!glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
|
|
checkgl!glBufferData(GL_ARRAY_BUFFER, _format.vertexSize * mesh.vertexCount, mesh.vertexData.ptr, GL_STATIC_DRAW);
|
|
checkgl!glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
// index buffer
|
|
checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
|
|
checkgl!glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.length * ushort.sizeof, indexData.ptr, GL_STATIC_DRAW);
|
|
checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
// vertex layout
|
|
//checkgl!glBindVertexArray(_vao);
|
|
// specify vertex buffer
|
|
//checkgl!glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
|
|
// specify index buffer
|
|
//checkgl!glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
|
|
|
|
//unbind();
|
|
}
|
|
|
|
/// draw mesh using specified effect
|
|
override void draw(GraphicsEffect effect, bool wireframe) {
|
|
//bind();
|
|
enableAttributes(effect);
|
|
foreach (fragment; _indexFragments) {
|
|
if (wireframe && fragment.type == PrimitiveType.triangles) {
|
|
// TODO: support wireframe not only for triangles
|
|
int triangleCount = fragment.end - fragment.start;
|
|
//triangleCount /= 3;
|
|
//checkgl!glDisable(GL_CULL_FACE);
|
|
for (int i = 0; i < triangleCount; i += 3) {
|
|
// GL line loop works strange; use GL_LINES instead
|
|
checkgl!glDrawRangeElements(GL_LINE_LOOP, //GL_TRIANGLES,
|
|
0, _vertexCount - 1, // The first to last vertex start, end
|
|
3, // count of indexes used to draw elements
|
|
GL_UNSIGNED_SHORT,
|
|
cast(char*)((fragment.start + i) * short.sizeof) // offset from index buffer beginning to fragment start
|
|
);
|
|
//checkgl!glDrawRangeElements(GL_LINES, //GL_TRIANGLES,
|
|
// 0, _vertexCount - 1, // The first to last vertex start, end
|
|
// 2, // count of indexes used to draw elements
|
|
// GL_UNSIGNED_SHORT,
|
|
// cast(char*)((fragment.start + i + 1) * short.sizeof) // offset from index buffer beginning to fragment start
|
|
// );
|
|
}
|
|
//checkgl!glEnable(GL_CULL_FACE);
|
|
} else {
|
|
checkgl!glDrawRangeElements(primitiveTypeToGL(fragment.type),
|
|
0, _vertexCount - 1, // The first to last vertex
|
|
fragment.end - fragment.start, // count of indexes used to draw elements
|
|
GL_UNSIGNED_SHORT,
|
|
cast(char*)(fragment.start * short.sizeof) // offset from index buffer beginning to fragment start
|
|
);
|
|
}
|
|
}
|
|
disableAttributes(effect);
|
|
//unbind();
|
|
}
|
|
}
|
|
|
|
class DummyVertexBuffer : VertexBuffer {
|
|
protected VertexFormat _format;
|
|
protected IndexFragment[] _indexFragments;
|
|
protected int _vertexCount;
|
|
protected const(float)[] _vertexData;
|
|
protected const(ushort)[] _indexData;
|
|
|
|
this() {
|
|
}
|
|
|
|
~this() {
|
|
}
|
|
|
|
///// bind into current context
|
|
//override void bind() {
|
|
//}
|
|
//
|
|
///// unbind from current context
|
|
//override void unbind() {
|
|
//}
|
|
|
|
/// update vertex element locations for effect/shader program
|
|
void enableAttributes(GraphicsEffect effect) {
|
|
int offset = 0;
|
|
for(int i = 0; i < _format.length; i++) {
|
|
int loc = effect.getVertexElementLocation(_format[i].type);
|
|
if (loc >= 0) {
|
|
checkgl!glVertexAttribPointer(loc, _format[i].size, GL_FLOAT, cast(ubyte)GL_FALSE, _format.vertexSize, cast(char*)(offset));
|
|
checkgl!glEnableVertexAttribArray(loc);
|
|
} else {
|
|
//Log.d("Attribute location not found for ", _format[i].type);
|
|
}
|
|
offset += _format[i].byteSize;
|
|
}
|
|
}
|
|
|
|
void disableAttributes(GraphicsEffect effect) {
|
|
for(int i = 0; i < _format.length; i++) {
|
|
int loc = effect.getVertexElementLocation(_format[i].type);
|
|
if (loc >= 0) {
|
|
checkgl!glDisableVertexAttribArray(loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// set or change data
|
|
override void setData(Mesh mesh) {
|
|
_format = mesh.vertexFormat;
|
|
_indexFragments = mesh.indexFragments;
|
|
_vertexCount = mesh.vertexCount;
|
|
_vertexData = mesh.vertexData;
|
|
_indexData = mesh.indexData;
|
|
}
|
|
|
|
/// draw mesh using specified effect
|
|
override void draw(GraphicsEffect effect, bool wireframe) {
|
|
//bind();
|
|
enableAttributes(effect);
|
|
foreach (fragment; _indexFragments) {
|
|
// TODO: support wireframe
|
|
checkgl!glDrawRangeElements(primitiveTypeToGL(fragment.type),
|
|
0, _vertexCount,
|
|
fragment.end - fragment.start,
|
|
GL_UNSIGNED_SHORT, cast(char*)(fragment.start * short.sizeof));
|
|
}
|
|
disableAttributes(effect);
|
|
//unbind();
|
|
}
|
|
}
|
|
|
|
GLenum primitiveTypeToGL(PrimitiveType type) {
|
|
switch(type) with (PrimitiveType) {
|
|
case triangles:
|
|
return GL_TRIANGLES;
|
|
case triangleStripes:
|
|
return GL_TRIANGLE_STRIP;
|
|
case lines:
|
|
return GL_LINES;
|
|
case lineStripes:
|
|
return GL_LINE_STRIP;
|
|
case points:
|
|
default:
|
|
return GL_POINTS;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// OpenGL GUI rendering queue. It collects gui draw calls, fills a big buffer for vertex data and draws everything
|
|
private final class OpenGLQueue {
|
|
|
|
OpenGLBatch[] batches;
|
|
|
|
/// OpenGL batch structure - to draw several triangles in single OpenGL call
|
|
private struct OpenGLBatch {
|
|
|
|
enum BatchType { Line = 0, Rect, Triangle, TexturedRect }
|
|
BatchType type;
|
|
|
|
Tex2D texture;
|
|
int textureDx;
|
|
int textureDy;
|
|
bool textureLinear;
|
|
|
|
// length of batch in indices
|
|
int length;
|
|
// offset in index buffer
|
|
int start;
|
|
}
|
|
|
|
// a big buffer
|
|
float[] _vertices;
|
|
float[] _colors;
|
|
float[] _texCoords;
|
|
int[] _indices;
|
|
|
|
|
|
/// draw all
|
|
void flush() {
|
|
glSupport.fillBuffers(_vertices, _colors, _texCoords, _indices);
|
|
foreach(b; batches) {
|
|
switch(b.type) with(OpenGLBatch.BatchType)
|
|
{
|
|
case Line: glSupport.drawLines(b.length, b.start); break;
|
|
case Rect: glSupport.drawSolidFillTriangles(b.length, b.start); break;
|
|
case Triangle: glSupport.drawSolidFillTriangles(b.length, b.start); break;
|
|
case TexturedRect: glSupport.drawColorAndTextureTriangles(b.texture, b.textureLinear, b.length, b.start); break;
|
|
default: break;
|
|
}
|
|
}
|
|
//Log.d(batches.length, " ", _vertices.length, " ", _colors.length, " ", _texCoords.length, " ", _indices.length);
|
|
glSupport.destroyBuffers();
|
|
destroy(batches);
|
|
destroy(_vertices);
|
|
destroy(_colors);
|
|
destroy(_texCoords);
|
|
destroy(_indices);
|
|
batches = null;
|
|
_vertices = null;
|
|
_colors = null;
|
|
_texCoords = null;
|
|
_indices = null;
|
|
}
|
|
|
|
static immutable float Z_2D = -2.0f;
|
|
|
|
/// add textured rectangle to queue
|
|
void addTexturedRect(Tex2D texture, int textureDx, int textureDy, uint color1, uint color2, uint color3, uint color4, Rect srcrc, Rect dstrc, bool linear) {
|
|
if (batches is null
|
|
|| batches[$-1].type != OpenGLBatch.BatchType.TexturedRect
|
|
|| batches[$-1].texture.ID != texture.ID
|
|
|| batches[$-1].textureLinear != linear)
|
|
{
|
|
batches ~= OpenGLBatch();
|
|
batches[$-1].type = OpenGLBatch.BatchType.TexturedRect;
|
|
batches[$-1].texture = texture;
|
|
batches[$-1].textureDx = textureDx;
|
|
batches[$-1].textureDy = textureDy;
|
|
batches[$-1].textureLinear = linear;
|
|
if(batches.length > 1)
|
|
batches[$-1].start = batches[$-2].start + batches[$-2].length;
|
|
}
|
|
|
|
float[] colors = convertColors([color1, color2, color3, color4]);
|
|
|
|
float dstx0 = cast(float)dstrc.left;
|
|
float dsty0 = cast(float)(glSupport.currentFBO ? dstrc.top : (glSupport.bufferDy - dstrc.top));
|
|
float dstx1 = cast(float)dstrc.right;
|
|
float dsty1 = cast(float)(glSupport.currentFBO ? dstrc.bottom : (glSupport.bufferDy - dstrc.bottom));
|
|
|
|
float srcx0 = srcrc.left / cast(float)textureDx;
|
|
float srcy0 = srcrc.top / cast(float)textureDy;
|
|
float srcx1 = srcrc.right / cast(float)textureDx;
|
|
float srcy1 = srcrc.bottom / cast(float)textureDy;
|
|
|
|
float[3 * 4] vertices = [
|
|
dstx0,dsty0,Z_2D,
|
|
dstx0,dsty1,Z_2D,
|
|
dstx1,dsty0,Z_2D,
|
|
dstx1,dsty1,Z_2D ];
|
|
|
|
float[2 * 4] texCoords = [srcx0,srcy0, srcx0,srcy1, srcx1,srcy0, srcx1,srcy1];
|
|
|
|
int[] indices = makeRectangleIndicesArray(cast(int)_vertices.length / 3);
|
|
|
|
mixin(calcLengthAndAppendAllToBuffer);
|
|
}
|
|
|
|
/// add solid rectangle to queue
|
|
void addSolidRect(Rect dstRect, uint color) {
|
|
addGradientRect(dstRect, color, color, color, color);
|
|
}
|
|
|
|
/// add gradient rectangle to queue
|
|
void addGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4) {
|
|
if (batches is null || batches[$-1].type != OpenGLBatch.BatchType.Rect) {
|
|
batches ~= OpenGLBatch();
|
|
batches[$-1].type = OpenGLBatch.BatchType.Rect;
|
|
if(batches.length > 1)
|
|
batches[$-1].start = batches[$-2].start + batches[$-2].length;
|
|
}
|
|
|
|
float[] colors = convertColors([color1, color2, color3, color4]);
|
|
|
|
float x0 = cast(float)(rc.left);
|
|
float y0 = cast(float)(glSupport.currentFBO ? rc.top : (glSupport.bufferDy - rc.top));
|
|
float x1 = cast(float)(rc.right);
|
|
float y1 = cast(float)(glSupport.currentFBO ? rc.bottom : (glSupport.bufferDy - rc.bottom));
|
|
|
|
float[3 * 4] vertices = [
|
|
x0,y0,Z_2D,
|
|
x0,y1,Z_2D,
|
|
x1,y0,Z_2D,
|
|
x1,y1,Z_2D ];
|
|
// fill texture coords buffer with zeros
|
|
float[2 * 4] texCoords = 0;
|
|
|
|
int[] indices = makeRectangleIndicesArray(cast(int)_vertices.length / 3);
|
|
|
|
mixin(calcLengthAndAppendAllToBuffer);
|
|
}
|
|
|
|
/// add triangle to queue
|
|
void addTriangle(PointF p1, PointF p2, PointF p3, uint color1, uint color2, uint color3) {
|
|
if (batches is null || batches[$-1].type != OpenGLBatch.BatchType.Triangle) {
|
|
batches ~= OpenGLBatch();
|
|
batches[$-1].type = OpenGLBatch.BatchType.Triangle;
|
|
if(batches.length > 1)
|
|
batches[$-1].start = batches[$-2].start + batches[$-2].length;
|
|
}
|
|
|
|
float[] colors = convertColors([color1, color2, color3]);
|
|
|
|
float x0 = p1.x;
|
|
float y0 = glSupport.currentFBO ? p1.y : (glSupport.bufferDy - p1.y);
|
|
float x1 = p2.x;
|
|
float y1 = glSupport.currentFBO ? p2.y : (glSupport.bufferDy - p2.y);
|
|
float x2 = p3.x;
|
|
float y2 = glSupport.currentFBO ? p3.y : (glSupport.bufferDy - p3.y);
|
|
|
|
float[3 * 3] vertices = [
|
|
x0,y0,Z_2D,
|
|
x1,y1,Z_2D,
|
|
x2,y2,Z_2D ];
|
|
// fill texture coords buffer with zeros
|
|
float[2 * 3] texCoords = 0;
|
|
|
|
int[] indices = makeTriangleIndicesArray(cast(int)_vertices.length / 3);
|
|
|
|
mixin(calcLengthAndAppendAllToBuffer);
|
|
}
|
|
|
|
/// add line to queue
|
|
/// rc is a line (left, top) - (right, bottom)
|
|
void addLine(Rect rc, uint color1, uint color2) {
|
|
if (batches is null || batches[$-1].type != OpenGLBatch.BatchType.Line) {
|
|
batches ~= OpenGLBatch();
|
|
batches[$-1].type = OpenGLBatch.BatchType.Line;
|
|
if(batches.length > 1)
|
|
batches[$-1].start = batches[$-2].start + batches[$-2].length;
|
|
}
|
|
|
|
float[] colors = convertColors([color1, color2]);
|
|
|
|
float x0 = cast(float)(rc.left);
|
|
float y0 = cast(float)(glSupport.currentFBO ? rc.top : (glSupport.bufferDy - rc.top));
|
|
float x1 = cast(float)(rc.right);
|
|
float y1 = cast(float)(glSupport.currentFBO ? rc.bottom : (glSupport.bufferDy - rc.bottom));
|
|
|
|
float[3 * 2] vertices = [
|
|
x0, y0, Z_2D,
|
|
x1, y1, Z_2D ];
|
|
// fill texture coords buffer with zeros
|
|
float[2 * 2] texCoords = 0;
|
|
|
|
int[] indices = makeLineIndicesArray(cast(int)_vertices.length / 3);
|
|
|
|
mixin(calcLengthAndAppendAllToBuffer);
|
|
}
|
|
|
|
enum calcLengthAndAppendAllToBuffer = q{
|
|
batches[$-1].length += cast(int)indices.length;
|
|
|
|
_vertices ~= vertices;
|
|
_colors ~= colors;
|
|
_texCoords ~= texCoords;
|
|
_indices ~= indices;
|
|
};
|
|
|
|
/// make indices for rectangle (2 triangles == 6 vertexes per rect)
|
|
int[6] makeRectangleIndicesArray(int offset) pure nothrow {
|
|
int[6] indices;
|
|
indices[0] = offset + 0;
|
|
indices[1] = offset + 1;
|
|
indices[2] = offset + 2;
|
|
indices[3] = offset + 1;
|
|
indices[4] = offset + 2;
|
|
indices[5] = offset + 3;
|
|
|
|
return indices;
|
|
}
|
|
|
|
/// make indices for triangles
|
|
int[3] makeTriangleIndicesArray(int offset) pure nothrow {
|
|
int[3] indices;
|
|
indices[0] = offset + 0;
|
|
indices[1] = offset + 1;
|
|
indices[2] = offset + 2;
|
|
return indices;
|
|
}
|
|
|
|
/// make indices for lines
|
|
int[2] makeLineIndicesArray(int offset) pure nothrow {
|
|
int[2] indices;
|
|
indices[0] = offset + 0;
|
|
indices[1] = offset + 1;
|
|
|
|
return indices;
|
|
}
|
|
}
|