4461 lines
173 KiB
C
4461 lines
173 KiB
C
/*
|
|
Copyright (c) 2016 Oculus VR, LLC.
|
|
Portions of macOS, iOS, functionality copyright (c) 2016 The Brenwill Workshop Ltd.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "gfxwrapper_opengl.h"
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
System level functionality
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
static void *AllocAlignedMemory(size_t size, size_t alignment) {
|
|
alignment = (alignment < sizeof(void *)) ? sizeof(void *) : alignment;
|
|
#if defined(OS_WINDOWS)
|
|
return _aligned_malloc(size, alignment);
|
|
#elif defined(OS_APPLE)
|
|
void *ptr = NULL;
|
|
return (posix_memalign(&ptr, alignment, size) == 0) ? ptr : NULL;
|
|
#else
|
|
return memalign(alignment, size);
|
|
#endif
|
|
}
|
|
|
|
static void FreeAlignedMemory(void *ptr) {
|
|
#if defined(OS_WINDOWS)
|
|
_aligned_free(ptr);
|
|
#else
|
|
free(ptr);
|
|
#endif
|
|
}
|
|
|
|
static void Print(const char *format, ...) {
|
|
#if defined(OS_WINDOWS)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf_s(buffer, 4096, _TRUNCATE, format, args);
|
|
va_end(args);
|
|
|
|
OutputDebugStringA(buffer);
|
|
#elif defined(OS_LINUX)
|
|
va_list args;
|
|
va_start(args, format);
|
|
vprintf(format, args);
|
|
va_end(args);
|
|
fflush(stdout);
|
|
#elif defined(OS_APPLE)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf(buffer, 4096, format, args);
|
|
va_end(args);
|
|
|
|
NSLog(@"%s", buffer);
|
|
#elif defined(OS_ANDROID)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf(buffer, 4096, format, args);
|
|
va_end(args);
|
|
|
|
__android_log_print(ANDROID_LOG_VERBOSE, "atw", "%s", buffer);
|
|
#endif
|
|
}
|
|
|
|
static void Error(const char *format, ...) {
|
|
#if defined(OS_WINDOWS)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf_s(buffer, 4096, _TRUNCATE, format, args);
|
|
va_end(args);
|
|
|
|
OutputDebugStringA(buffer);
|
|
|
|
// MessageBoxA(NULL, buffer, "ERROR", MB_OK | MB_ICONINFORMATION);
|
|
#elif defined(OS_LINUX)
|
|
va_list args;
|
|
va_start(args, format);
|
|
vprintf(format, args);
|
|
va_end(args);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
#elif defined(OS_APPLE_MACOS)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
int length = vsnprintf(buffer, 4096, format, args);
|
|
va_end(args);
|
|
|
|
NSLog(@"%s\n", buffer);
|
|
|
|
if ([NSThread isMainThread]) {
|
|
NSString *string = [[NSString alloc] initWithBytes:buffer length:length encoding:NSASCIIStringEncoding];
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
[alert addButtonWithTitle:@"OK"];
|
|
[alert setMessageText:@"Error"];
|
|
[alert setInformativeText:string];
|
|
[alert setAlertStyle:NSWarningAlertStyle];
|
|
[alert runModal];
|
|
#pragma GCC diagnostic pop
|
|
}
|
|
#elif defined(OS_APPLE_IOS)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
int length = vsnprintf(buffer, 4096, format, args);
|
|
va_end(args);
|
|
|
|
NSLog(@"%s\n", buffer);
|
|
|
|
if ([NSThread isMainThread]) {
|
|
NSString *string = [[NSString alloc] initWithBytes:buffer length:length encoding:NSASCIIStringEncoding];
|
|
UIAlertController *alert =
|
|
[UIAlertController alertControllerWithTitle:@"Error" message:string preferredStyle:UIAlertControllerStyleAlert];
|
|
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction *action){
|
|
}]];
|
|
[UIApplication.sharedApplication.keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
|
|
}
|
|
#elif defined(OS_ANDROID)
|
|
char buffer[4096];
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf(buffer, 4096, format, args);
|
|
va_end(args);
|
|
|
|
__android_log_print(ANDROID_LOG_ERROR, "atw", "%s", buffer);
|
|
#endif
|
|
// Without exiting, the application will likely crash.
|
|
if (format != NULL) {
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
Frame logging.
|
|
|
|
Each thread that calls ksFrameLog_Open will open its own log.
|
|
A frame log is always opened for a specified number of frames, and will
|
|
automatically close after the specified number of frames have been recorded.
|
|
The CPU and GPU times for the recorded frames will be listed at the end of the log.
|
|
|
|
ksFrameLog
|
|
|
|
static void ksFrameLog_Open( const char * fileName, const int frameCount );
|
|
static void ksFrameLog_Write( const char * fileName, const int lineNumber, const char * function );
|
|
static void ksFrameLog_BeginFrame();
|
|
static void ksFrameLog_EndFrame( const ksNanoseconds cpuTimeNanoseconds, const ksNanoseconds gpuTimeNanoseconds, const int
|
|
gpuTimeFramesDelayed );
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
FILE *fp;
|
|
ksNanoseconds *frameCpuTimes;
|
|
ksNanoseconds *frameGpuTimes;
|
|
int frameCount;
|
|
int frame;
|
|
} ksFrameLog;
|
|
|
|
__thread ksFrameLog *threadFrameLog;
|
|
|
|
static ksFrameLog *ksFrameLog_Get() {
|
|
ksFrameLog *l = threadFrameLog;
|
|
if (l == NULL) {
|
|
l = (ksFrameLog *)malloc(sizeof(ksFrameLog));
|
|
memset(l, 0, sizeof(ksFrameLog));
|
|
threadFrameLog = l;
|
|
}
|
|
return l;
|
|
}
|
|
|
|
static void ksFrameLog_Open(const char *fileName, const int frameCount) {
|
|
ksFrameLog *l = ksFrameLog_Get();
|
|
if (l != NULL && l->fp == NULL) {
|
|
l->fp = fopen(fileName, "wb");
|
|
if (l->fp == NULL) {
|
|
Print("Failed to open %s\n", fileName);
|
|
} else {
|
|
Print("Opened frame log %s for %d frames.\n", fileName, frameCount);
|
|
l->frameCpuTimes = (ksNanoseconds *)malloc(frameCount * sizeof(l->frameCpuTimes[0]));
|
|
l->frameGpuTimes = (ksNanoseconds *)malloc(frameCount * sizeof(l->frameGpuTimes[0]));
|
|
memset(l->frameCpuTimes, 0, frameCount * sizeof(l->frameCpuTimes[0]));
|
|
memset(l->frameGpuTimes, 0, frameCount * sizeof(l->frameGpuTimes[0]));
|
|
l->frameCount = frameCount;
|
|
l->frame = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ksFrameLog_Write(const char *fileName, const int lineNumber, const char *function) {
|
|
ksFrameLog *l = ksFrameLog_Get();
|
|
if (l != NULL && l->fp != NULL) {
|
|
if (l->frame < l->frameCount) {
|
|
fprintf(l->fp, "%s(%d): %s\r\n", fileName, lineNumber, function);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ksFrameLog_BeginFrame() {
|
|
ksFrameLog *l = ksFrameLog_Get();
|
|
if (l != NULL && l->fp != NULL) {
|
|
if (l->frame < l->frameCount) {
|
|
#if !defined(NDEBUG)
|
|
fprintf(l->fp, "================ BEGIN FRAME %d ================\r\n", l->frame);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ksFrameLog_EndFrame(const ksNanoseconds cpuTimeNanoseconds, const ksNanoseconds gpuTimeNanoseconds,
|
|
const int gpuTimeFramesDelayed) {
|
|
ksFrameLog *l = ksFrameLog_Get();
|
|
if (l != NULL && l->fp != NULL) {
|
|
if (l->frame < l->frameCount) {
|
|
l->frameCpuTimes[l->frame] = cpuTimeNanoseconds;
|
|
#if !defined(NDEBUG)
|
|
fprintf(l->fp, "================ END FRAME %d ================\r\n", l->frame);
|
|
#endif
|
|
}
|
|
if (l->frame >= gpuTimeFramesDelayed && l->frame < l->frameCount + gpuTimeFramesDelayed) {
|
|
l->frameGpuTimes[l->frame - gpuTimeFramesDelayed] = gpuTimeNanoseconds;
|
|
}
|
|
|
|
l->frame++;
|
|
|
|
if (l->frame >= l->frameCount + gpuTimeFramesDelayed) {
|
|
for (int i = 0; i < l->frameCount; i++) {
|
|
fprintf(l->fp, "frame %d: CPU = %1.1f ms, GPU = %1.1f ms\r\n", i, l->frameCpuTimes[i] * 1e-6f,
|
|
l->frameGpuTimes[i] * 1e-6f);
|
|
}
|
|
|
|
Print("Closing frame log file (%d frames).\n", l->frameCount);
|
|
fclose(l->fp);
|
|
free(l->frameCpuTimes);
|
|
free(l->frameGpuTimes);
|
|
memset(l, 0, sizeof(ksFrameLog));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
OpenGL error checking.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
#if !defined(NDEBUG)
|
|
#define GL(func) \
|
|
func; \
|
|
ksFrameLog_Write(__FILE__, __LINE__, #func); \
|
|
GlCheckErrors(#func);
|
|
#else
|
|
#define GL(func) func;
|
|
#endif
|
|
|
|
#if !defined(NDEBUG)
|
|
#define EGL(func) \
|
|
ksFrameLog_Write(__FILE__, __LINE__, #func); \
|
|
if (func == EGL_FALSE) { \
|
|
Error(#func " failed: %s", EglErrorString(eglGetError())); \
|
|
}
|
|
#else
|
|
#define EGL(func) \
|
|
if (func == EGL_FALSE) { \
|
|
Error(#func " failed: %s", EglErrorString(eglGetError())); \
|
|
}
|
|
#endif
|
|
|
|
#if defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
static const char *EglErrorString(const EGLint error) {
|
|
switch (error) {
|
|
case EGL_SUCCESS:
|
|
return "EGL_SUCCESS";
|
|
case EGL_NOT_INITIALIZED:
|
|
return "EGL_NOT_INITIALIZED";
|
|
case EGL_BAD_ACCESS:
|
|
return "EGL_BAD_ACCESS";
|
|
case EGL_BAD_ALLOC:
|
|
return "EGL_BAD_ALLOC";
|
|
case EGL_BAD_ATTRIBUTE:
|
|
return "EGL_BAD_ATTRIBUTE";
|
|
case EGL_BAD_CONTEXT:
|
|
return "EGL_BAD_CONTEXT";
|
|
case EGL_BAD_CONFIG:
|
|
return "EGL_BAD_CONFIG";
|
|
case EGL_BAD_CURRENT_SURFACE:
|
|
return "EGL_BAD_CURRENT_SURFACE";
|
|
case EGL_BAD_DISPLAY:
|
|
return "EGL_BAD_DISPLAY";
|
|
case EGL_BAD_SURFACE:
|
|
return "EGL_BAD_SURFACE";
|
|
case EGL_BAD_MATCH:
|
|
return "EGL_BAD_MATCH";
|
|
case EGL_BAD_PARAMETER:
|
|
return "EGL_BAD_PARAMETER";
|
|
case EGL_BAD_NATIVE_PIXMAP:
|
|
return "EGL_BAD_NATIVE_PIXMAP";
|
|
case EGL_BAD_NATIVE_WINDOW:
|
|
return "EGL_BAD_NATIVE_WINDOW";
|
|
case EGL_CONTEXT_LOST:
|
|
return "EGL_CONTEXT_LOST";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static const char *GlErrorString(GLenum error) {
|
|
switch (error) {
|
|
case GL_NO_ERROR:
|
|
return "GL_NO_ERROR";
|
|
case GL_INVALID_ENUM:
|
|
return "GL_INVALID_ENUM";
|
|
case GL_INVALID_VALUE:
|
|
return "GL_INVALID_VALUE";
|
|
case GL_INVALID_OPERATION:
|
|
return "GL_INVALID_OPERATION";
|
|
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
|
return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
|
case GL_OUT_OF_MEMORY:
|
|
return "GL_OUT_OF_MEMORY";
|
|
#if !defined(OS_APPLE_MACOS) && !defined(OS_ANDROID) && !defined(OS_APPLE_IOS)
|
|
case GL_STACK_UNDERFLOW:
|
|
return "GL_STACK_UNDERFLOW";
|
|
case GL_STACK_OVERFLOW:
|
|
return "GL_STACK_OVERFLOW";
|
|
#endif
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static const char *GlFramebufferStatusString(GLenum status) {
|
|
switch (status) {
|
|
case GL_FRAMEBUFFER_UNDEFINED:
|
|
return "GL_FRAMEBUFFER_UNDEFINED";
|
|
case GL_FRAMEBUFFER_UNSUPPORTED:
|
|
return "GL_FRAMEBUFFER_UNSUPPORTED";
|
|
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
|
return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
|
|
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
|
return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
|
|
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
|
|
return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
|
|
#if !defined(OS_ANDROID) && !defined(OS_APPLE_IOS)
|
|
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
|
return "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER";
|
|
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
|
return "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER";
|
|
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
|
|
return "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS";
|
|
#endif
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void GlCheckErrors(const char *function) {
|
|
for (int i = 0; i < 10; i++) {
|
|
const GLenum error = glGetError();
|
|
if (error == GL_NO_ERROR) {
|
|
break;
|
|
}
|
|
Error("GL error: %s: %s", function, GlErrorString(error));
|
|
}
|
|
}
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
OpenGL extensions.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
bool timer_query; // GL_ARB_timer_query, GL_EXT_disjoint_timer_query
|
|
bool texture_clamp_to_border; // GL_EXT_texture_border_clamp, GL_OES_texture_border_clamp
|
|
bool buffer_storage; // GL_ARB_buffer_storage
|
|
bool multi_sampled_storage; // GL_ARB_texture_storage_multisample
|
|
bool multi_view; // GL_OVR_multiview, GL_OVR_multiview2
|
|
bool multi_sampled_resolve; // GL_EXT_multisampled_render_to_texture
|
|
bool multi_view_multi_sampled_resolve; // GL_OVR_multiview_multisampled_render_to_texture
|
|
|
|
int texture_clamp_to_border_id;
|
|
} ksOpenGLExtensions;
|
|
|
|
ksOpenGLExtensions glExtensions;
|
|
|
|
/*
|
|
================================
|
|
Get proc address / extensions
|
|
================================
|
|
*/
|
|
|
|
#if defined(OS_WINDOWS)
|
|
PROC GetExtension(const char *functionName) { return wglGetProcAddress(functionName); }
|
|
#elif defined(OS_APPLE)
|
|
void (*GetExtension(const char *functionName))() { return NULL; }
|
|
#elif defined(OS_LINUX_XCB) || defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
void (*GetExtension(const char *functionName))() { return glXGetProcAddress((const GLubyte *)functionName); }
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
void (*GetExtension(const char *functionName))() { return eglGetProcAddress(functionName); }
|
|
#endif
|
|
|
|
GLint glGetInteger(GLenum pname) {
|
|
GLint i;
|
|
GL(glGetIntegerv(pname, &i));
|
|
return i;
|
|
}
|
|
|
|
static bool GlCheckExtension(const char *extension) {
|
|
#if defined(OS_WINDOWS) || defined(OS_LINUX)
|
|
PFNGLGETSTRINGIPROC glGetStringi = (PFNGLGETSTRINGIPROC)GetExtension("glGetStringi");
|
|
#endif
|
|
GL(const GLint numExtensions = glGetInteger(GL_NUM_EXTENSIONS));
|
|
for (int i = 0; i < numExtensions; i++) {
|
|
GL(const GLubyte *string = glGetStringi(GL_EXTENSIONS, i));
|
|
if (strcmp((const char *)string, extension) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if defined(OS_WINDOWS) || defined(OS_LINUX)
|
|
|
|
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
|
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
|
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
|
PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
|
PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
|
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
|
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
|
PFNGLISRENDERBUFFERPROC glIsRenderbuffer;
|
|
PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
|
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample;
|
|
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
|
|
PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
|
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
|
PFNGLFRAMEBUFFERTEXTURELAYERPROC glFramebufferTextureLayer;
|
|
PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
|
|
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
|
|
PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glFramebufferTextureMultisampleMultiviewOVR;
|
|
PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
|
PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC glCheckNamedFramebufferStatus;
|
|
|
|
PFNGLGENBUFFERSPROC glGenBuffers;
|
|
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
|
PFNGLBINDBUFFERPROC glBindBuffer;
|
|
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
|
PFNGLBUFFERDATAPROC glBufferData;
|
|
PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
|
PFNGLBUFFERSTORAGEPROC glBufferStorage;
|
|
PFNGLMAPBUFFERPROC glMapBuffer;
|
|
PFNGLMAPBUFFERRANGEPROC glMapBufferRange;
|
|
PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
|
|
|
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
|
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
|
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
|
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
|
|
PFNGLVERTEXATTRIBDIVISORPROC glVertexAttribDivisor;
|
|
PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
|
|
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
|
|
|
|
#if defined(OS_WINDOWS)
|
|
PFNGLACTIVETEXTUREPROC glActiveTexture;
|
|
PFNGLTEXIMAGE3DPROC glTexImage3D;
|
|
PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D;
|
|
PFNGLCOMPRESSEDTEXIMAGE3DPROC glCompressedTexImage3D;
|
|
PFNGLTEXSUBIMAGE3DPROC glTexSubImage3D;
|
|
PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glCompressedTexSubImage2D;
|
|
PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glCompressedTexSubImage3D;
|
|
#endif
|
|
PFNGLTEXSTORAGE2DPROC glTexStorage2D;
|
|
PFNGLTEXSTORAGE3DPROC glTexStorage3D;
|
|
PFNGLTEXIMAGE2DMULTISAMPLEPROC glTexImage2DMultisample;
|
|
PFNGLTEXIMAGE3DMULTISAMPLEPROC glTexImage3DMultisample;
|
|
PFNGLTEXSTORAGE2DMULTISAMPLEPROC glTexStorage2DMultisample;
|
|
PFNGLTEXSTORAGE3DMULTISAMPLEPROC glTexStorage3DMultisample;
|
|
PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
|
|
PFNGLBINDIMAGETEXTUREPROC glBindImageTexture;
|
|
|
|
PFNGLCREATEPROGRAMPROC glCreateProgram;
|
|
PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
|
PFNGLCREATESHADERPROC glCreateShader;
|
|
PFNGLDELETESHADERPROC glDeleteShader;
|
|
PFNGLSHADERSOURCEPROC glShaderSource;
|
|
PFNGLCOMPILESHADERPROC glCompileShader;
|
|
PFNGLGETSHADERIVPROC glGetShaderiv;
|
|
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
|
PFNGLUSEPROGRAMPROC glUseProgram;
|
|
PFNGLATTACHSHADERPROC glAttachShader;
|
|
PFNGLLINKPROGRAMPROC glLinkProgram;
|
|
PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
|
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
|
PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation;
|
|
PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
|
|
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
|
PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
|
PFNGLGETPROGRAMRESOURCEINDEXPROC glGetProgramResourceIndex;
|
|
PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
|
PFNGLSHADERSTORAGEBLOCKBINDINGPROC glShaderStorageBlockBinding;
|
|
PFNGLPROGRAMUNIFORM1IPROC glProgramUniform1i;
|
|
PFNGLUNIFORM1IPROC glUniform1i;
|
|
PFNGLUNIFORM1IVPROC glUniform1iv;
|
|
PFNGLUNIFORM2IVPROC glUniform2iv;
|
|
PFNGLUNIFORM3IVPROC glUniform3iv;
|
|
PFNGLUNIFORM4IVPROC glUniform4iv;
|
|
PFNGLUNIFORM1FPROC glUniform1f;
|
|
PFNGLUNIFORM1FVPROC glUniform1fv;
|
|
PFNGLUNIFORM2FVPROC glUniform2fv;
|
|
PFNGLUNIFORM3FVPROC glUniform3fv;
|
|
PFNGLUNIFORM4FVPROC glUniform4fv;
|
|
PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv;
|
|
PFNGLUNIFORMMATRIX2X3FVPROC glUniformMatrix2x3fv;
|
|
PFNGLUNIFORMMATRIX2X4FVPROC glUniformMatrix2x4fv;
|
|
PFNGLUNIFORMMATRIX3X2FVPROC glUniformMatrix3x2fv;
|
|
PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv;
|
|
PFNGLUNIFORMMATRIX3X4FVPROC glUniformMatrix3x4fv;
|
|
PFNGLUNIFORMMATRIX4X2FVPROC glUniformMatrix4x2fv;
|
|
PFNGLUNIFORMMATRIX4X3FVPROC glUniformMatrix4x3fv;
|
|
PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
|
|
|
|
PFNGLDRAWELEMENTSINSTANCEDPROC glDrawElementsInstanced;
|
|
PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
|
PFNGLMEMORYBARRIERPROC glMemoryBarrier;
|
|
|
|
PFNGLGENQUERIESPROC glGenQueries;
|
|
PFNGLDELETEQUERIESPROC glDeleteQueries;
|
|
PFNGLISQUERYPROC glIsQuery;
|
|
PFNGLBEGINQUERYPROC glBeginQuery;
|
|
PFNGLENDQUERYPROC glEndQuery;
|
|
PFNGLQUERYCOUNTERPROC glQueryCounter;
|
|
PFNGLGETQUERYIVPROC glGetQueryiv;
|
|
PFNGLGETQUERYOBJECTIVPROC glGetQueryObjectiv;
|
|
PFNGLGETQUERYOBJECTUIVPROC glGetQueryObjectuiv;
|
|
PFNGLGETQUERYOBJECTI64VPROC glGetQueryObjecti64v;
|
|
PFNGLGETQUERYOBJECTUI64VPROC glGetQueryObjectui64v;
|
|
|
|
PFNGLFENCESYNCPROC glFenceSync;
|
|
PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
|
|
PFNGLDELETESYNCPROC glDeleteSync;
|
|
PFNGLISSYNCPROC glIsSync;
|
|
|
|
PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate;
|
|
PFNGLBLENDEQUATIONSEPARATEPROC glBlendEquationSeparate;
|
|
|
|
PFNGLDEBUGMESSAGECONTROLPROC glDebugMessageControl;
|
|
PFNGLDEBUGMESSAGECALLBACKPROC glDebugMessageCallback;
|
|
|
|
#if defined(OS_WINDOWS)
|
|
PFNGLBLENDCOLORPROC glBlendColor;
|
|
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
|
|
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
|
|
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
|
|
PFNWGLDELAYBEFORESWAPNVPROC wglDelayBeforeSwapNV;
|
|
#elif defined(OS_LINUX) && !defined(OS_LINUX_WAYLAND)
|
|
PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
|
|
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
|
|
PFNGLXDELAYBEFORESWAPNVPROC glXDelayBeforeSwapNV;
|
|
#endif
|
|
|
|
void GlBootstrapExtensions() {
|
|
#if defined(OS_WINDOWS)
|
|
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)GetExtension("wglChoosePixelFormatARB");
|
|
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)GetExtension("wglCreateContextAttribsARB");
|
|
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)GetExtension("wglSwapIntervalEXT");
|
|
wglDelayBeforeSwapNV = (PFNWGLDELAYBEFORESWAPNVPROC)GetExtension("wglDelayBeforeSwapNV");
|
|
#elif defined(OS_LINUX) && !defined(OS_LINUX_WAYLAND)
|
|
glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)GetExtension("glXCreateContextAttribsARB");
|
|
glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)GetExtension("glXSwapIntervalEXT");
|
|
glXDelayBeforeSwapNV = (PFNGLXDELAYBEFORESWAPNVPROC)GetExtension("glXDelayBeforeSwapNV");
|
|
#endif
|
|
}
|
|
|
|
void GlInitExtensions() {
|
|
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)GetExtension("glGenFramebuffers");
|
|
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)GetExtension("glDeleteFramebuffers");
|
|
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)GetExtension("glBindFramebuffer");
|
|
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)GetExtension("glBlitFramebuffer");
|
|
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)GetExtension("glGenRenderbuffers");
|
|
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)GetExtension("glDeleteRenderbuffers");
|
|
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)GetExtension("glBindRenderbuffer");
|
|
glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)GetExtension("glIsRenderbuffer");
|
|
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)GetExtension("glRenderbufferStorage");
|
|
glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)GetExtension("glRenderbufferStorageMultisample");
|
|
glRenderbufferStorageMultisampleEXT =
|
|
(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)GetExtension("glRenderbufferStorageMultisampleEXT");
|
|
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)GetExtension("glFramebufferRenderbuffer");
|
|
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)GetExtension("glFramebufferTexture2D");
|
|
glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)GetExtension("glFramebufferTextureLayer");
|
|
glFramebufferTexture2DMultisampleEXT =
|
|
(PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)GetExtension("glFramebufferTexture2DMultisampleEXT");
|
|
glFramebufferTextureMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)GetExtension("glFramebufferTextureMultiviewOVR");
|
|
glFramebufferTextureMultisampleMultiviewOVR =
|
|
(PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)GetExtension("glFramebufferTextureMultisampleMultiviewOVR");
|
|
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)GetExtension("glCheckFramebufferStatus");
|
|
glCheckNamedFramebufferStatus = (PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC)GetExtension("glCheckNamedFramebufferStatus");
|
|
|
|
glGenBuffers = (PFNGLGENBUFFERSPROC)GetExtension("glGenBuffers");
|
|
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)GetExtension("glDeleteBuffers");
|
|
glBindBuffer = (PFNGLBINDBUFFERPROC)GetExtension("glBindBuffer");
|
|
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)GetExtension("glBindBufferBase");
|
|
glBufferData = (PFNGLBUFFERDATAPROC)GetExtension("glBufferData");
|
|
glBufferSubData = (PFNGLBUFFERSUBDATAPROC)GetExtension("glBufferSubData");
|
|
glBufferStorage = (PFNGLBUFFERSTORAGEPROC)GetExtension("glBufferStorage");
|
|
glMapBuffer = (PFNGLMAPBUFFERPROC)GetExtension("glMapBuffer");
|
|
glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)GetExtension("glMapBufferRange");
|
|
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)GetExtension("glUnmapBuffer");
|
|
|
|
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)GetExtension("glGenVertexArrays");
|
|
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)GetExtension("glDeleteVertexArrays");
|
|
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)GetExtension("glBindVertexArray");
|
|
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)GetExtension("glVertexAttribPointer");
|
|
glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)GetExtension("glVertexAttribDivisor");
|
|
glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)GetExtension("glDisableVertexAttribArray");
|
|
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)GetExtension("glEnableVertexAttribArray");
|
|
|
|
#if defined(OS_WINDOWS)
|
|
glActiveTexture = (PFNGLACTIVETEXTUREPROC)GetExtension("glActiveTexture");
|
|
glTexImage3D = (PFNGLTEXIMAGE3DPROC)GetExtension("glTexImage3D");
|
|
glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)GetExtension("glCompressedTexImage2D ");
|
|
glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)GetExtension("glCompressedTexImage3D ");
|
|
glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)GetExtension("glTexSubImage3D");
|
|
glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)GetExtension("glCompressedTexSubImage2D");
|
|
glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)GetExtension("glCompressedTexSubImage3D");
|
|
#endif
|
|
glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)GetExtension("glTexStorage2D");
|
|
glTexStorage3D = (PFNGLTEXSTORAGE3DPROC)GetExtension("glTexStorage3D");
|
|
glTexImage2DMultisample = (PFNGLTEXIMAGE2DMULTISAMPLEPROC)GetExtension("glTexImage2DMultisample");
|
|
glTexImage3DMultisample = (PFNGLTEXIMAGE3DMULTISAMPLEPROC)GetExtension("glTexImage3DMultisample");
|
|
glTexStorage2DMultisample = (PFNGLTEXSTORAGE2DMULTISAMPLEPROC)GetExtension("glTexStorage2DMultisample");
|
|
glTexStorage3DMultisample = (PFNGLTEXSTORAGE3DMULTISAMPLEPROC)GetExtension("glTexStorage3DMultisample");
|
|
glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)GetExtension("glGenerateMipmap");
|
|
glBindImageTexture = (PFNGLBINDIMAGETEXTUREPROC)GetExtension("glBindImageTexture");
|
|
|
|
glCreateProgram = (PFNGLCREATEPROGRAMPROC)GetExtension("glCreateProgram");
|
|
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)GetExtension("glDeleteProgram");
|
|
glCreateShader = (PFNGLCREATESHADERPROC)GetExtension("glCreateShader");
|
|
glDeleteShader = (PFNGLDELETESHADERPROC)GetExtension("glDeleteShader");
|
|
glShaderSource = (PFNGLSHADERSOURCEPROC)GetExtension("glShaderSource");
|
|
glCompileShader = (PFNGLCOMPILESHADERPROC)GetExtension("glCompileShader");
|
|
glGetShaderiv = (PFNGLGETSHADERIVPROC)GetExtension("glGetShaderiv");
|
|
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)GetExtension("glGetShaderInfoLog");
|
|
glUseProgram = (PFNGLUSEPROGRAMPROC)GetExtension("glUseProgram");
|
|
glAttachShader = (PFNGLATTACHSHADERPROC)GetExtension("glAttachShader");
|
|
glLinkProgram = (PFNGLLINKPROGRAMPROC)GetExtension("glLinkProgram");
|
|
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)GetExtension("glGetProgramiv");
|
|
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)GetExtension("glGetProgramInfoLog");
|
|
glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)GetExtension("glGetAttribLocation");
|
|
glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)GetExtension("glBindAttribLocation");
|
|
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)GetExtension("glGetUniformLocation");
|
|
glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)GetExtension("glGetUniformBlockIndex");
|
|
glProgramUniform1i = (PFNGLPROGRAMUNIFORM1IPROC)GetExtension("glProgramUniform1i");
|
|
glUniform1i = (PFNGLUNIFORM1IPROC)GetExtension("glUniform1i");
|
|
glUniform1iv = (PFNGLUNIFORM1IVPROC)GetExtension("glUniform1iv");
|
|
glUniform2iv = (PFNGLUNIFORM2IVPROC)GetExtension("glUniform2iv");
|
|
glUniform3iv = (PFNGLUNIFORM3IVPROC)GetExtension("glUniform3iv");
|
|
glUniform4iv = (PFNGLUNIFORM4IVPROC)GetExtension("glUniform4iv");
|
|
glUniform1f = (PFNGLUNIFORM1FPROC)GetExtension("glUniform1f");
|
|
glUniform1fv = (PFNGLUNIFORM1FVPROC)GetExtension("glUniform1fv");
|
|
glUniform2fv = (PFNGLUNIFORM2FVPROC)GetExtension("glUniform2fv");
|
|
glUniform3fv = (PFNGLUNIFORM3FVPROC)GetExtension("glUniform3fv");
|
|
glUniform4fv = (PFNGLUNIFORM4FVPROC)GetExtension("glUniform4fv");
|
|
glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)GetExtension("glUniformMatrix3fv");
|
|
glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)GetExtension("glUniformMatrix2x3fv");
|
|
glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)GetExtension("glUniformMatrix2x4fv");
|
|
glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)GetExtension("glUniformMatrix3x2fv");
|
|
glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)GetExtension("glUniformMatrix3fv");
|
|
glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)GetExtension("glUniformMatrix3x4fv");
|
|
glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)GetExtension("glUniformMatrix4x2fv");
|
|
glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)GetExtension("glUniformMatrix4x3fv");
|
|
glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)GetExtension("glUniformMatrix4fv");
|
|
glGetProgramResourceIndex = (PFNGLGETPROGRAMRESOURCEINDEXPROC)GetExtension("glGetProgramResourceIndex");
|
|
glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)GetExtension("glUniformBlockBinding");
|
|
glShaderStorageBlockBinding = (PFNGLSHADERSTORAGEBLOCKBINDINGPROC)GetExtension("glShaderStorageBlockBinding");
|
|
|
|
glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDPROC)GetExtension("glDrawElementsInstanced");
|
|
glDispatchCompute = (PFNGLDISPATCHCOMPUTEPROC)GetExtension("glDispatchCompute");
|
|
glMemoryBarrier = (PFNGLMEMORYBARRIERPROC)GetExtension("glMemoryBarrier");
|
|
|
|
glGenQueries = (PFNGLGENQUERIESPROC)GetExtension("glGenQueries");
|
|
glDeleteQueries = (PFNGLDELETEQUERIESPROC)GetExtension("glDeleteQueries");
|
|
glIsQuery = (PFNGLISQUERYPROC)GetExtension("glIsQuery");
|
|
glBeginQuery = (PFNGLBEGINQUERYPROC)GetExtension("glBeginQuery");
|
|
glEndQuery = (PFNGLENDQUERYPROC)GetExtension("glEndQuery");
|
|
glQueryCounter = (PFNGLQUERYCOUNTERPROC)GetExtension("glQueryCounter");
|
|
glGetQueryiv = (PFNGLGETQUERYIVPROC)GetExtension("glGetQueryiv");
|
|
glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)GetExtension("glGetQueryObjectiv");
|
|
glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)GetExtension("glGetQueryObjectuiv");
|
|
glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VPROC)GetExtension("glGetQueryObjecti64v");
|
|
glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VPROC)GetExtension("glGetQueryObjectui64v");
|
|
|
|
glFenceSync = (PFNGLFENCESYNCPROC)GetExtension("glFenceSync");
|
|
glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)GetExtension("glClientWaitSync");
|
|
glDeleteSync = (PFNGLDELETESYNCPROC)GetExtension("glDeleteSync");
|
|
glIsSync = (PFNGLISSYNCPROC)GetExtension("glIsSync");
|
|
|
|
glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)GetExtension("glBlendFuncSeparate");
|
|
glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)GetExtension("glBlendEquationSeparate");
|
|
|
|
#if defined(OS_WINDOWS)
|
|
glBlendColor = (PFNGLBLENDCOLORPROC)GetExtension("glBlendColor");
|
|
#endif
|
|
|
|
glDebugMessageControl = (PFNGLDEBUGMESSAGECONTROLPROC)GetExtension("glDebugMessageControl");
|
|
glDebugMessageCallback = (PFNGLDEBUGMESSAGECALLBACKPROC)GetExtension("glDebugMessageCallback");
|
|
|
|
glExtensions.timer_query = GlCheckExtension("GL_EXT_timer_query");
|
|
glExtensions.texture_clamp_to_border = true; // always available
|
|
glExtensions.buffer_storage =
|
|
GlCheckExtension("GL_EXT_buffer_storage") || (OPENGL_VERSION_MAJOR * 10 + OPENGL_VERSION_MINOR >= 44);
|
|
glExtensions.multi_sampled_storage =
|
|
GlCheckExtension("GL_ARB_texture_storage_multisample") || (OPENGL_VERSION_MAJOR * 10 + OPENGL_VERSION_MINOR >= 43);
|
|
glExtensions.multi_view = GlCheckExtension("GL_OVR_multiview2");
|
|
glExtensions.multi_sampled_resolve = GlCheckExtension("GL_EXT_multisampled_render_to_texture");
|
|
glExtensions.multi_view_multi_sampled_resolve = GlCheckExtension("GL_OVR_multiview_multisampled_render_to_texture");
|
|
|
|
glExtensions.texture_clamp_to_border_id = GL_CLAMP_TO_BORDER;
|
|
}
|
|
|
|
#elif defined(OS_APPLE_MACOS)
|
|
|
|
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
|
|
PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glFramebufferTextureMultisampleMultiviewOVR;
|
|
PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
|
|
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
|
|
|
|
void GlInitExtensions() {
|
|
glExtensions.timer_query = GlCheckExtension("GL_EXT_timer_query");
|
|
glExtensions.texture_clamp_to_border = true; // always available
|
|
glExtensions.buffer_storage =
|
|
GlCheckExtension("GL_EXT_buffer_storage") || (OPENGL_VERSION_MAJOR * 10 + OPENGL_VERSION_MINOR >= 44);
|
|
glExtensions.multi_sampled_storage =
|
|
GlCheckExtension("GL_ARB_texture_storage_multisample") || (OPENGL_VERSION_MAJOR * 10 + OPENGL_VERSION_MINOR >= 43);
|
|
glExtensions.multi_view = GlCheckExtension("GL_OVR_multiview2");
|
|
glExtensions.multi_sampled_resolve = GlCheckExtension("GL_EXT_multisampled_render_to_texture");
|
|
glExtensions.multi_view_multi_sampled_resolve = GlCheckExtension("GL_OVR_multiview_multisampled_render_to_texture");
|
|
|
|
glExtensions.texture_clamp_to_border_id = GL_CLAMP_TO_BORDER;
|
|
}
|
|
|
|
#elif defined(OS_ANDROID)
|
|
|
|
// GL_EXT_disjoint_timer_query without _EXT
|
|
#if !defined(GL_TIMESTAMP)
|
|
#define GL_QUERY_COUNTER_BITS GL_QUERY_COUNTER_BITS_EXT
|
|
#define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
|
|
#define GL_TIMESTAMP GL_TIMESTAMP_EXT
|
|
#define GL_GPU_DISJOINT GL_GPU_DISJOINT_EXT
|
|
#endif
|
|
|
|
// GL_EXT_buffer_storage without _EXT
|
|
#if !defined(GL_BUFFER_STORAGE_FLAGS)
|
|
#define GL_MAP_READ_BIT 0x0001 // GL_MAP_READ_BIT_EXT
|
|
#define GL_MAP_WRITE_BIT 0x0002 // GL_MAP_WRITE_BIT_EXT
|
|
#define GL_MAP_PERSISTENT_BIT 0x0040 // GL_MAP_PERSISTENT_BIT_EXT
|
|
#define GL_MAP_COHERENT_BIT 0x0080 // GL_MAP_COHERENT_BIT_EXT
|
|
#define GL_DYNAMIC_STORAGE_BIT 0x0100 // GL_DYNAMIC_STORAGE_BIT_EXT
|
|
#define GL_CLIENT_STORAGE_BIT 0x0200 // GL_CLIENT_STORAGE_BIT_EXT
|
|
#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 // GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT_EXT
|
|
#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F // GL_BUFFER_IMMUTABLE_STORAGE_EXT
|
|
#define GL_BUFFER_STORAGE_FLAGS 0x8220 // GL_BUFFER_STORAGE_FLAGS_EXT
|
|
#endif
|
|
|
|
typedef void(GL_APIENTRY *PFNGLBUFFERSTORAGEEXTPROC)(GLenum target, GLsizeiptr size, const void *data, GLbitfield flags);
|
|
typedef void(GL_APIENTRY *PFNGLTEXSTORAGE3DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
|
|
GLsizei height, GLsizei depth, GLboolean fixedsamplelocations);
|
|
|
|
// EGL_KHR_fence_sync, GL_OES_EGL_sync, VG_KHR_EGL_sync
|
|
PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
|
|
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
|
|
PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
|
|
PFNEGLGETSYNCATTRIBKHRPROC eglGetSyncAttribKHR;
|
|
|
|
// GL_EXT_disjoint_timer_query
|
|
PFNGLQUERYCOUNTEREXTPROC glQueryCounter;
|
|
PFNGLGETQUERYOBJECTI64VEXTPROC glGetQueryObjecti64v;
|
|
PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
|
|
|
|
// GL_EXT_buffer_storage
|
|
PFNGLBUFFERSTORAGEEXTPROC glBufferStorage;
|
|
|
|
// GL_OVR_multiview
|
|
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
|
|
|
|
// GL_EXT_multisampled_render_to_texture
|
|
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
|
|
PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
|
|
|
|
// GL_OVR_multiview_multisampled_render_to_texture
|
|
PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glFramebufferTextureMultisampleMultiviewOVR;
|
|
|
|
#ifndef GL_ES_VERSION_3_2
|
|
PFNGLTEXSTORAGE3DMULTISAMPLEPROC glTexStorage3DMultisample;
|
|
#endif
|
|
|
|
#if !defined(EGL_OPENGL_ES3_BIT)
|
|
#define EGL_OPENGL_ES3_BIT 0x0040
|
|
#endif
|
|
|
|
// GL_EXT_texture_cube_map_array
|
|
#if !defined(GL_TEXTURE_CUBE_MAP_ARRAY)
|
|
#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009
|
|
#endif
|
|
|
|
// GL_EXT_texture_filter_anisotropic
|
|
#if !defined(GL_TEXTURE_MAX_ANISOTROPY_EXT)
|
|
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
|
|
#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
|
|
#endif
|
|
|
|
// GL_EXT_texture_border_clamp or GL_OES_texture_border_clamp
|
|
#if !defined(GL_CLAMP_TO_BORDER)
|
|
#define GL_CLAMP_TO_BORDER 0x812D
|
|
#endif
|
|
|
|
// No 1D textures in OpenGL ES.
|
|
#if !defined(GL_TEXTURE_1D)
|
|
#define GL_TEXTURE_1D 0x0DE0
|
|
#endif
|
|
|
|
// No 1D texture arrays in OpenGL ES.
|
|
#if !defined(GL_TEXTURE_1D_ARRAY)
|
|
#define GL_TEXTURE_1D_ARRAY 0x8C18
|
|
#endif
|
|
|
|
// No multi-sampled texture arrays in OpenGL ES.
|
|
#if !defined(GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
|
|
#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102
|
|
#endif
|
|
|
|
void GlInitExtensions() {
|
|
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)GetExtension("eglCreateSyncKHR");
|
|
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)GetExtension("eglDestroySyncKHR");
|
|
eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)GetExtension("eglClientWaitSyncKHR");
|
|
eglGetSyncAttribKHR = (PFNEGLGETSYNCATTRIBKHRPROC)GetExtension("eglGetSyncAttribKHR");
|
|
|
|
glQueryCounter = (PFNGLQUERYCOUNTEREXTPROC)GetExtension("glQueryCounterEXT");
|
|
glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VEXTPROC)GetExtension("glGetQueryObjecti64vEXT");
|
|
glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VEXTPROC)GetExtension("glGetQueryObjectui64vEXT");
|
|
|
|
glBufferStorage = (PFNGLBUFFERSTORAGEEXTPROC)GetExtension("glBufferStorageEXT");
|
|
|
|
glRenderbufferStorageMultisampleEXT =
|
|
(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)GetExtension("glRenderbufferStorageMultisampleEXT");
|
|
glFramebufferTexture2DMultisampleEXT =
|
|
(PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)GetExtension("glFramebufferTexture2DMultisampleEXT");
|
|
glFramebufferTextureMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)GetExtension("glFramebufferTextureMultiviewOVR");
|
|
glFramebufferTextureMultisampleMultiviewOVR =
|
|
(PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)GetExtension("glFramebufferTextureMultisampleMultiviewOVR");
|
|
|
|
#ifndef GL_ES_VERSION_3_2
|
|
glTexStorage3DMultisample = (PFNGLTEXSTORAGE3DMULTISAMPLEPROC)GetExtension("glTexStorage3DMultisample");
|
|
#endif
|
|
|
|
glExtensions.timer_query = GlCheckExtension("GL_EXT_disjoint_timer_query");
|
|
glExtensions.texture_clamp_to_border =
|
|
GlCheckExtension("GL_EXT_texture_border_clamp") || GlCheckExtension("GL_OES_texture_border_clamp");
|
|
glExtensions.buffer_storage = GlCheckExtension("GL_EXT_buffer_storage");
|
|
glExtensions.multi_view = GlCheckExtension("GL_OVR_multiview2");
|
|
glExtensions.multi_sampled_resolve = GlCheckExtension("GL_EXT_multisampled_render_to_texture");
|
|
glExtensions.multi_view_multi_sampled_resolve = GlCheckExtension("GL_OVR_multiview_multisampled_render_to_texture");
|
|
|
|
glExtensions.texture_clamp_to_border_id =
|
|
(GlCheckExtension("GL_OES_texture_border_clamp")
|
|
? GL_CLAMP_TO_BORDER
|
|
: (GlCheckExtension("GL_EXT_texture_border_clamp") ? GL_CLAMP_TO_BORDER : (GL_CLAMP_TO_EDGE)));
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
Driver Instance.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
bool ksDriverInstance_Create(ksDriverInstance *instance) {
|
|
memset(instance, 0, sizeof(ksDriverInstance));
|
|
return true;
|
|
}
|
|
|
|
void ksDriverInstance_Destroy(ksDriverInstance *instance) { memset(instance, 0, sizeof(ksDriverInstance)); }
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
GPU Device.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
bool ksGpuDevice_Create(ksGpuDevice *device, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo) {
|
|
/*
|
|
Use an extensions to select the appropriate device:
|
|
https://www.opengl.org/registry/specs/NV/gpu_affinity.txt
|
|
https://www.opengl.org/registry/specs/AMD/wgl_gpu_association.txt
|
|
https://www.opengl.org/registry/specs/AMD/glx_gpu_association.txt
|
|
|
|
On Linux configure each GPU to use a separate X screen and then select
|
|
the X screen to render to.
|
|
*/
|
|
|
|
memset(device, 0, sizeof(ksGpuDevice));
|
|
|
|
device->instance = instance;
|
|
device->queueInfo = *queueInfo;
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuDevice_Destroy(ksGpuDevice *device) { memset(device, 0, sizeof(ksGpuDevice)); }
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
GPU Context.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
ksGpuSurfaceBits ksGpuContext_BitsForSurfaceFormat(const ksGpuSurfaceColorFormat colorFormat,
|
|
const ksGpuSurfaceDepthFormat depthFormat) {
|
|
ksGpuSurfaceBits bits;
|
|
bits.redBits = ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R8G8B8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B8G8R8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R5G6B5)
|
|
? 5
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B5G6R5) ? 5 : 8))));
|
|
bits.greenBits = ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R8G8B8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B8G8R8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R5G6B5)
|
|
? 6
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B5G6R5) ? 6 : 8))));
|
|
bits.blueBits = ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R8G8B8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B8G8R8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R5G6B5)
|
|
? 5
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B5G6R5) ? 5 : 8))));
|
|
bits.alphaBits = ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R8G8B8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B8G8R8A8)
|
|
? 8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R5G6B5)
|
|
? 0
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B5G6R5) ? 0 : 8))));
|
|
bits.colorBits = bits.redBits + bits.greenBits + bits.blueBits + bits.alphaBits;
|
|
bits.depthBits =
|
|
((depthFormat == KS_GPU_SURFACE_DEPTH_FORMAT_D16) ? 16 : ((depthFormat == KS_GPU_SURFACE_DEPTH_FORMAT_D24) ? 24 : 0));
|
|
return bits;
|
|
}
|
|
|
|
GLenum ksGpuContext_InternalSurfaceColorFormat(const ksGpuSurfaceColorFormat colorFormat) {
|
|
return ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R8G8B8A8)
|
|
? GL_RGBA8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B8G8R8A8)
|
|
? GL_RGBA8
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_R5G6B5)
|
|
? GL_RGB565
|
|
: ((colorFormat == KS_GPU_SURFACE_COLOR_FORMAT_B5G6R5) ? GL_RGB565 : GL_RGBA8))));
|
|
}
|
|
|
|
GLenum ksGpuContext_InternalSurfaceDepthFormat(const ksGpuSurfaceDepthFormat depthFormat) {
|
|
return ((depthFormat == KS_GPU_SURFACE_DEPTH_FORMAT_D16)
|
|
? GL_DEPTH_COMPONENT16
|
|
: ((depthFormat == KS_GPU_SURFACE_DEPTH_FORMAT_D24) ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT24));
|
|
}
|
|
|
|
#if defined(OS_WINDOWS)
|
|
|
|
static bool ksGpuContext_CreateForSurface(ksGpuContext *context, const ksGpuDevice *device, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, HINSTANCE hInstance, HDC hDC) {
|
|
UNUSED_PARM(queueIndex);
|
|
|
|
context->device = device;
|
|
|
|
const ksGpuSurfaceBits bits = ksGpuContext_BitsForSurfaceFormat(colorFormat, depthFormat);
|
|
|
|
PIXELFORMATDESCRIPTOR pfd = {
|
|
sizeof(PIXELFORMATDESCRIPTOR),
|
|
1, // version
|
|
PFD_DRAW_TO_WINDOW | // must support windowed
|
|
PFD_SUPPORT_OPENGL | // must support OpenGL
|
|
PFD_DOUBLEBUFFER, // must support double buffering
|
|
PFD_TYPE_RGBA, // iPixelType
|
|
bits.colorBits, // cColorBits
|
|
0,
|
|
0, // cRedBits, cRedShift
|
|
0,
|
|
0, // cGreenBits, cGreenShift
|
|
0,
|
|
0, // cBlueBits, cBlueShift
|
|
0,
|
|
0, // cAlphaBits, cAlphaShift
|
|
0, // cAccumBits
|
|
0, // cAccumRedBits
|
|
0, // cAccumGreenBits
|
|
0, // cAccumBlueBits
|
|
0, // cAccumAlphaBits
|
|
bits.depthBits, // cDepthBits
|
|
0, // cStencilBits
|
|
0, // cAuxBuffers
|
|
PFD_MAIN_PLANE, // iLayerType
|
|
0, // bReserved
|
|
0, // dwLayerMask
|
|
0, // dwVisibleMask
|
|
0 // dwDamageMask
|
|
};
|
|
|
|
HWND localWnd = NULL;
|
|
HDC localDC = hDC;
|
|
|
|
if (sampleCount > KS_GPU_SAMPLE_COUNT_1) {
|
|
// A valid OpenGL context is needed to get OpenGL extensions including wglChoosePixelFormatARB
|
|
// and wglCreateContextAttribsARB. A device context with a valid pixel format is needed to create
|
|
// an OpenGL context. However, once a pixel format is set on a device context it is final.
|
|
// Therefore a pixel format is set on the device context of a temporary window to create a context
|
|
// to get the extensions for multi-sampling.
|
|
localWnd = CreateWindow(APPLICATION_NAME, "temp", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
|
|
localDC = GetDC(localWnd);
|
|
}
|
|
|
|
int pixelFormat = ChoosePixelFormat(localDC, &pfd);
|
|
if (pixelFormat == 0) {
|
|
Error("Failed to find a suitable pixel format.");
|
|
return false;
|
|
}
|
|
|
|
if (!SetPixelFormat(localDC, pixelFormat, &pfd)) {
|
|
Error("Failed to set the pixel format.");
|
|
return false;
|
|
}
|
|
|
|
// Now that the pixel format is set, create a temporary context to get the extensions.
|
|
{
|
|
HGLRC hGLRC = wglCreateContext(localDC);
|
|
wglMakeCurrent(localDC, hGLRC);
|
|
|
|
GlBootstrapExtensions();
|
|
|
|
wglMakeCurrent(NULL, NULL);
|
|
wglDeleteContext(hGLRC);
|
|
}
|
|
|
|
if (sampleCount > KS_GPU_SAMPLE_COUNT_1) {
|
|
// Release the device context and destroy the window that were created to get extensions.
|
|
ReleaseDC(localWnd, localDC);
|
|
DestroyWindow(localWnd);
|
|
|
|
int pixelFormatAttribs[] = {WGL_DRAW_TO_WINDOW_ARB,
|
|
GL_TRUE,
|
|
WGL_SUPPORT_OPENGL_ARB,
|
|
GL_TRUE,
|
|
WGL_DOUBLE_BUFFER_ARB,
|
|
GL_TRUE,
|
|
WGL_PIXEL_TYPE_ARB,
|
|
WGL_TYPE_RGBA_ARB,
|
|
WGL_COLOR_BITS_ARB,
|
|
bits.colorBits,
|
|
WGL_DEPTH_BITS_ARB,
|
|
bits.depthBits,
|
|
WGL_SAMPLE_BUFFERS_ARB,
|
|
1,
|
|
WGL_SAMPLES_ARB,
|
|
sampleCount,
|
|
0};
|
|
|
|
unsigned int numPixelFormats = 0;
|
|
|
|
if (!wglChoosePixelFormatARB(hDC, pixelFormatAttribs, NULL, 1, &pixelFormat, &numPixelFormats) || numPixelFormats == 0) {
|
|
Error("Failed to find MSAA pixel format.");
|
|
return false;
|
|
}
|
|
|
|
memset(&pfd, 0, sizeof(pfd));
|
|
|
|
if (!DescribePixelFormat(hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) {
|
|
Error("Failed to describe the pixel format.");
|
|
return false;
|
|
}
|
|
|
|
if (!SetPixelFormat(hDC, pixelFormat, &pfd)) {
|
|
Error("Failed to set the pixel format.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int contextAttribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB,
|
|
OPENGL_VERSION_MAJOR,
|
|
WGL_CONTEXT_MINOR_VERSION_ARB,
|
|
OPENGL_VERSION_MINOR,
|
|
WGL_CONTEXT_PROFILE_MASK_ARB,
|
|
WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
|
WGL_CONTEXT_FLAGS_ARB,
|
|
WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB,
|
|
0};
|
|
|
|
context->hDC = hDC;
|
|
context->hGLRC = wglCreateContextAttribsARB(hDC, NULL, contextAttribs);
|
|
if (!context->hGLRC) {
|
|
Error("Failed to create GL context.");
|
|
return false;
|
|
}
|
|
|
|
wglMakeCurrent(hDC, context->hGLRC);
|
|
|
|
GlInitExtensions();
|
|
|
|
return true;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
|
|
static int glxGetFBConfigAttrib2(Display *dpy, GLXFBConfig config, int attribute) {
|
|
int value;
|
|
glXGetFBConfigAttrib(dpy, config, attribute, &value);
|
|
return value;
|
|
}
|
|
|
|
static bool ksGpuContext_CreateForSurface(ksGpuContext *context, const ksGpuDevice *device, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, Display *xDisplay, int xScreen) {
|
|
UNUSED_PARM(queueIndex);
|
|
|
|
context->device = device;
|
|
|
|
int glxErrorBase;
|
|
int glxEventBase;
|
|
if (!glXQueryExtension(xDisplay, &glxErrorBase, &glxEventBase)) {
|
|
Error("X display does not support the GLX extension.");
|
|
return false;
|
|
}
|
|
|
|
int glxVersionMajor;
|
|
int glxVersionMinor;
|
|
if (!glXQueryVersion(xDisplay, &glxVersionMajor, &glxVersionMinor)) {
|
|
Error("Unable to retrieve GLX version.");
|
|
return false;
|
|
}
|
|
|
|
int fbConfigCount = 0;
|
|
GLXFBConfig *fbConfigs = glXGetFBConfigs(xDisplay, xScreen, &fbConfigCount);
|
|
if (fbConfigCount == 0) {
|
|
Error("No valid framebuffer configurations found.");
|
|
return false;
|
|
}
|
|
|
|
const ksGpuSurfaceBits bits = ksGpuContext_BitsForSurfaceFormat(colorFormat, depthFormat);
|
|
|
|
bool foundFbConfig = false;
|
|
for (int i = 0; i < fbConfigCount; i++) {
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_FBCONFIG_ID) == 0) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_VISUAL_ID) == 0) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_DOUBLEBUFFER) == 0) {
|
|
continue;
|
|
}
|
|
if ((glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_RENDER_TYPE) & GLX_RGBA_BIT) == 0) {
|
|
continue;
|
|
}
|
|
if ((glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT) == 0) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_RED_SIZE) != bits.redBits) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_GREEN_SIZE) != bits.greenBits) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_BLUE_SIZE) != bits.blueBits) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_ALPHA_SIZE) != bits.alphaBits) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_DEPTH_SIZE) != bits.depthBits) {
|
|
continue;
|
|
}
|
|
if (sampleCount > KS_GPU_SAMPLE_COUNT_1) {
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_SAMPLE_BUFFERS) != 1) {
|
|
continue;
|
|
}
|
|
if (glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_SAMPLES) != (int)sampleCount) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
context->visualid = glxGetFBConfigAttrib2(xDisplay, fbConfigs[i], GLX_VISUAL_ID);
|
|
context->glxFBConfig = fbConfigs[i];
|
|
foundFbConfig = true;
|
|
break;
|
|
}
|
|
|
|
XFree(fbConfigs);
|
|
|
|
if (!foundFbConfig) {
|
|
Error("Failed to to find desired framebuffer configuration.");
|
|
return false;
|
|
}
|
|
|
|
context->xDisplay = xDisplay;
|
|
|
|
int attribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB,
|
|
OPENGL_VERSION_MAJOR,
|
|
GLX_CONTEXT_MINOR_VERSION_ARB,
|
|
OPENGL_VERSION_MINOR,
|
|
GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
|
|
GLX_CONTEXT_FLAGS_ARB,
|
|
GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
|
0};
|
|
|
|
glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)GetExtension("glXCreateContextAttribsARB");
|
|
|
|
context->glxContext = glXCreateContextAttribsARB(xDisplay, // Display * dpy
|
|
context->glxFBConfig, // GLXFBConfig config
|
|
NULL, // GLXContext share_context
|
|
True, // Bool direct
|
|
attribs); // const int * attrib_list
|
|
|
|
if (context->glxContext == NULL) {
|
|
Error("Unable to create GLX context.");
|
|
return false;
|
|
}
|
|
|
|
if (!glXIsDirect(xDisplay, context->glxContext)) {
|
|
Error("Unable to create direct rendering context.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_XCB)
|
|
|
|
static uint32_t xcb_glx_get_property(const uint32_t *properties, const uint32_t numProperties, uint32_t propertyName) {
|
|
for (uint32_t i = 0; i < numProperties; i++) {
|
|
if (properties[i * 2 + 0] == propertyName) {
|
|
return properties[i * 2 + 1];
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool ksGpuContext_CreateForSurface(ksGpuContext *context, const ksGpuDevice *device, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, xcb_connection_t *connection, int screen_number) {
|
|
UNUSED_PARM(queueIndex);
|
|
|
|
context->device = device;
|
|
|
|
GlInitExtensions();
|
|
|
|
xcb_glx_query_version_cookie_t glx_query_version_cookie =
|
|
xcb_glx_query_version(connection, OPENGL_VERSION_MAJOR, OPENGL_VERSION_MINOR);
|
|
xcb_glx_query_version_reply_t *glx_query_version_reply =
|
|
xcb_glx_query_version_reply(connection, glx_query_version_cookie, NULL);
|
|
if (glx_query_version_reply == NULL) {
|
|
Error("Unable to retrieve GLX version.");
|
|
return false;
|
|
}
|
|
free(glx_query_version_reply);
|
|
|
|
xcb_glx_get_fb_configs_cookie_t get_fb_configs_cookie = xcb_glx_get_fb_configs(connection, screen_number);
|
|
xcb_glx_get_fb_configs_reply_t *get_fb_configs_reply = xcb_glx_get_fb_configs_reply(connection, get_fb_configs_cookie, NULL);
|
|
|
|
if (get_fb_configs_reply == NULL || get_fb_configs_reply->num_FB_configs == 0) {
|
|
Error("No valid framebuffer configurations found.");
|
|
return false;
|
|
}
|
|
|
|
const ksGpuSurfaceBits bits = ksGpuContext_BitsForSurfaceFormat(colorFormat, depthFormat);
|
|
|
|
const uint32_t *fb_configs_properties = xcb_glx_get_fb_configs_property_list(get_fb_configs_reply);
|
|
const uint32_t fb_configs_num_properties = get_fb_configs_reply->num_properties;
|
|
|
|
bool foundFbConfig = false;
|
|
for (uint32_t i = 0; i < get_fb_configs_reply->num_FB_configs; i++) {
|
|
const uint32_t *fb_config = fb_configs_properties + i * fb_configs_num_properties * 2;
|
|
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_FBCONFIG_ID) == 0) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_VISUAL_ID) == 0) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_DOUBLEBUFFER) == 0) {
|
|
continue;
|
|
}
|
|
if ((xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_RENDER_TYPE) & GLX_RGBA_BIT) == 0) {
|
|
continue;
|
|
}
|
|
if ((xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT) == 0) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_RED_SIZE) != bits.redBits) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_GREEN_SIZE) != bits.greenBits) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_BLUE_SIZE) != bits.blueBits) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_ALPHA_SIZE) != bits.alphaBits) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_DEPTH_SIZE) != bits.depthBits) {
|
|
continue;
|
|
}
|
|
if (sampleCount > KS_GPU_SAMPLE_COUNT_1) {
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_SAMPLE_BUFFERS) != 1) {
|
|
continue;
|
|
}
|
|
if (xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_SAMPLES) != sampleCount) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
context->fbconfigid = xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_FBCONFIG_ID);
|
|
context->visualid = xcb_glx_get_property(fb_config, fb_configs_num_properties, GLX_VISUAL_ID);
|
|
foundFbConfig = true;
|
|
break;
|
|
}
|
|
|
|
free(get_fb_configs_reply);
|
|
|
|
if (!foundFbConfig) {
|
|
Error("Failed to to find desired framebuffer configuration.");
|
|
return false;
|
|
}
|
|
|
|
context->connection = connection;
|
|
context->screen_number = screen_number;
|
|
|
|
// Create the context.
|
|
uint32_t attribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB,
|
|
OPENGL_VERSION_MAJOR,
|
|
GLX_CONTEXT_MINOR_VERSION_ARB,
|
|
OPENGL_VERSION_MINOR,
|
|
GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
|
|
GLX_CONTEXT_FLAGS_ARB,
|
|
GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
|
0};
|
|
|
|
context->glxContext = xcb_generate_id(connection);
|
|
xcb_glx_create_context_attribs_arb(connection, // xcb_connection_t * connection
|
|
context->glxContext, // xcb_glx_context_t context
|
|
context->fbconfigid, // xcb_glx_fbconfig_t fbconfig
|
|
screen_number, // uint32_t screen
|
|
0, // xcb_glx_context_t share_list
|
|
1, // uint8_t is_direct
|
|
4, // uint32_t num_attribs
|
|
attribs); // const uint32_t * attribs
|
|
|
|
// Make sure the context is direct.
|
|
xcb_generic_error_t *error;
|
|
xcb_glx_is_direct_cookie_t glx_is_direct_cookie = xcb_glx_is_direct_unchecked(connection, context->glxContext);
|
|
xcb_glx_is_direct_reply_t *glx_is_direct_reply = xcb_glx_is_direct_reply(connection, glx_is_direct_cookie, &error);
|
|
const bool is_direct = (glx_is_direct_reply != NULL && glx_is_direct_reply->is_direct);
|
|
free(glx_is_direct_reply);
|
|
|
|
if (!is_direct) {
|
|
Error("Unable to create direct rendering context.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_WAYLAND)
|
|
|
|
static bool ksGpuContext_CreateForSurface(ksGpuContext *context, const ksGpuDevice *device, struct wl_display *native_display) {
|
|
context->device = device;
|
|
|
|
EGLint numConfigs;
|
|
EGLint majorVersion;
|
|
EGLint minorVersion;
|
|
|
|
EGLint fbAttribs[] = {EGL_SURFACE_TYPE,
|
|
EGL_WINDOW_BIT,
|
|
EGL_RENDERABLE_TYPE,
|
|
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
|
EGL_RED_SIZE,
|
|
8,
|
|
EGL_GREEN_SIZE,
|
|
8,
|
|
EGL_BLUE_SIZE,
|
|
8,
|
|
EGL_NONE};
|
|
|
|
EGLint contextAttribs[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
|
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
|
EGL_CONTEXT_CLIENT_VERSION,
|
|
OPENGL_VERSION_MAJOR,
|
|
EGL_CONTEXT_MINOR_VERSION,
|
|
OPENGL_VERSION_MINOR,
|
|
EGL_NONE};
|
|
|
|
context->display = eglGetDisplay(native_display);
|
|
if (context->display == EGL_NO_DISPLAY) {
|
|
Error("Could not create EGL Display.");
|
|
return false;
|
|
}
|
|
|
|
if (!eglInitialize(context->display, &majorVersion, &minorVersion)) {
|
|
Error("eglInitialize failed.");
|
|
return false;
|
|
}
|
|
|
|
printf("Initialized EGL context version %d.%d\n", majorVersion, minorVersion);
|
|
|
|
EGLBoolean ret = eglGetConfigs(context->display, NULL, 0, &numConfigs);
|
|
if (ret != EGL_TRUE || numConfigs == 0) {
|
|
Error("eglGetConfigs failed.");
|
|
return false;
|
|
}
|
|
|
|
ret = eglChooseConfig(context->display, fbAttribs, &context->config, 1, &numConfigs);
|
|
if (ret != EGL_TRUE || numConfigs != 1) {
|
|
Error("eglChooseConfig failed.");
|
|
return false;
|
|
}
|
|
|
|
context->mainSurface = eglCreateWindowSurface(context->display, context->config, context->native_window, NULL);
|
|
if (context->mainSurface == EGL_NO_SURFACE) {
|
|
Error("eglCreateWindowSurface failed");
|
|
return false;
|
|
}
|
|
|
|
eglBindAPI(EGL_OPENGL_API);
|
|
|
|
context->context = eglCreateContext(context->display, context->config, EGL_NO_CONTEXT, contextAttribs);
|
|
if (context->context == EGL_NO_CONTEXT) {
|
|
Error("Could not create OpenGL context.");
|
|
return false;
|
|
}
|
|
|
|
if (!eglMakeCurrent(context->display, context->mainSurface, context->mainSurface, context->context)) {
|
|
Error("Could not make the current context current.");
|
|
return false;
|
|
}
|
|
|
|
GlInitExtensions();
|
|
|
|
return true;
|
|
}
|
|
|
|
#elif defined(OS_APPLE_MACOS)
|
|
|
|
static bool ksGpuContext_CreateForSurface(ksGpuContext *context, const ksGpuDevice *device, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, CGDirectDisplayID display) {
|
|
UNUSED_PARM(queueIndex);
|
|
|
|
context->device = device;
|
|
|
|
const ksGpuSurfaceBits bits = ksGpuContext_BitsForSurfaceFormat(colorFormat, depthFormat);
|
|
|
|
NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = {NSOpenGLPFAMinimumPolicy,
|
|
1,
|
|
NSOpenGLPFAScreenMask,
|
|
CGDisplayIDToOpenGLDisplayMask(display),
|
|
NSOpenGLPFAAccelerated,
|
|
NSOpenGLPFAOpenGLProfile,
|
|
NSOpenGLProfileVersion3_2Core,
|
|
NSOpenGLPFADoubleBuffer,
|
|
NSOpenGLPFAColorSize,
|
|
bits.colorBits,
|
|
NSOpenGLPFADepthSize,
|
|
bits.depthBits,
|
|
NSOpenGLPFASampleBuffers,
|
|
(sampleCount > KS_GPU_SAMPLE_COUNT_1),
|
|
NSOpenGLPFASamples,
|
|
sampleCount,
|
|
0};
|
|
|
|
NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes] autorelease];
|
|
if (pixelFormat == nil) {
|
|
return false;
|
|
}
|
|
context->nsContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
|
|
if (context->nsContext == nil) {
|
|
return false;
|
|
}
|
|
|
|
context->cglContext = [context->nsContext CGLContextObj];
|
|
|
|
GlInitExtensions();
|
|
|
|
return true;
|
|
}
|
|
|
|
#elif defined(OS_ANDROID)
|
|
|
|
static bool ksGpuContext_CreateForSurface(ksGpuContext *context, const ksGpuDevice *device, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, EGLDisplay display) {
|
|
context->device = device;
|
|
|
|
context->display = display;
|
|
|
|
// Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample
|
|
// flags in eglChooseConfig when the user has selected the "force 4x MSAA" option in
|
|
// settings, and that is completely wasted on the time warped frontbuffer.
|
|
const int MAX_CONFIGS = 1024;
|
|
EGLConfig configs[MAX_CONFIGS];
|
|
EGLint numConfigs = 0;
|
|
EGL(eglGetConfigs(display, configs, MAX_CONFIGS, &numConfigs));
|
|
|
|
const ksGpuSurfaceBits bits = ksGpuContext_BitsForSurfaceFormat(colorFormat, depthFormat);
|
|
|
|
const EGLint configAttribs[] = {EGL_RED_SIZE, bits.greenBits, EGL_GREEN_SIZE, bits.redBits, EGL_BLUE_SIZE, bits.blueBits,
|
|
EGL_ALPHA_SIZE, bits.alphaBits, EGL_DEPTH_SIZE, bits.depthBits,
|
|
// EGL_STENCIL_SIZE, 0,
|
|
EGL_SAMPLE_BUFFERS, (sampleCount > KS_GPU_SAMPLE_COUNT_1), EGL_SAMPLES,
|
|
(sampleCount > KS_GPU_SAMPLE_COUNT_1) ? sampleCount : 0, EGL_NONE};
|
|
|
|
context->config = 0;
|
|
for (int i = 0; i < numConfigs; i++) {
|
|
EGLint value = 0;
|
|
|
|
eglGetConfigAttrib(display, configs[i], EGL_RENDERABLE_TYPE, &value);
|
|
if ((value & EGL_OPENGL_ES3_BIT) != EGL_OPENGL_ES3_BIT) {
|
|
continue;
|
|
}
|
|
|
|
// Without EGL_KHR_surfaceless_context, the config needs to support both pbuffers and window surfaces.
|
|
eglGetConfigAttrib(display, configs[i], EGL_SURFACE_TYPE, &value);
|
|
if ((value & (EGL_WINDOW_BIT | EGL_PBUFFER_BIT)) != (EGL_WINDOW_BIT | EGL_PBUFFER_BIT)) {
|
|
continue;
|
|
}
|
|
|
|
int j = 0;
|
|
for (; configAttribs[j] != EGL_NONE; j += 2) {
|
|
eglGetConfigAttrib(display, configs[i], configAttribs[j], &value);
|
|
if (value != configAttribs[j + 1]) {
|
|
break;
|
|
}
|
|
}
|
|
if (configAttribs[j] == EGL_NONE) {
|
|
context->config = configs[i];
|
|
break;
|
|
}
|
|
}
|
|
if (context->config == 0) {
|
|
Error("Failed to find EGLConfig");
|
|
return false;
|
|
}
|
|
|
|
EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, OPENGL_VERSION_MAJOR, EGL_NONE, EGL_NONE, EGL_NONE};
|
|
// Use the default priority if KS_GPU_QUEUE_PRIORITY_MEDIUM is selected.
|
|
const ksGpuQueuePriority priority = device->queueInfo.queuePriorities[queueIndex];
|
|
if (priority != KS_GPU_QUEUE_PRIORITY_MEDIUM) {
|
|
contextAttribs[2] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
|
|
contextAttribs[3] = (priority == KS_GPU_QUEUE_PRIORITY_LOW) ? EGL_CONTEXT_PRIORITY_LOW_IMG : EGL_CONTEXT_PRIORITY_HIGH_IMG;
|
|
}
|
|
context->context = eglCreateContext(display, context->config, EGL_NO_CONTEXT, contextAttribs);
|
|
if (context->context == EGL_NO_CONTEXT) {
|
|
Error("eglCreateContext() failed: %s", EglErrorString(eglGetError()));
|
|
return false;
|
|
}
|
|
|
|
const EGLint surfaceAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
|
|
context->tinySurface = eglCreatePbufferSurface(display, context->config, surfaceAttribs);
|
|
if (context->tinySurface == EGL_NO_SURFACE) {
|
|
Error("eglCreatePbufferSurface() failed: %s", EglErrorString(eglGetError()));
|
|
eglDestroyContext(display, context->context);
|
|
context->context = EGL_NO_CONTEXT;
|
|
return false;
|
|
}
|
|
context->mainSurface = context->tinySurface;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool ksGpuContext_CreateShared(ksGpuContext *context, const ksGpuContext *other, int queueIndex) {
|
|
UNUSED_PARM(queueIndex);
|
|
|
|
memset(context, 0, sizeof(ksGpuContext));
|
|
|
|
context->device = other->device;
|
|
|
|
#if defined(OS_WINDOWS)
|
|
context->hDC = other->hDC;
|
|
context->hGLRC = wglCreateContext(other->hDC);
|
|
if (!wglShareLists(other->hGLRC, context->hGLRC)) {
|
|
return false;
|
|
}
|
|
#elif defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
context->xDisplay = other->xDisplay;
|
|
context->visualid = other->visualid;
|
|
context->glxFBConfig = other->glxFBConfig;
|
|
context->glxDrawable = other->glxDrawable;
|
|
context->glxContext = glXCreateNewContext(other->xDisplay, other->glxFBConfig, GLX_RGBA_TYPE, other->glxContext, True);
|
|
if (context->glxContext == NULL) {
|
|
return false;
|
|
}
|
|
#elif defined(OS_LINUX_XCB)
|
|
context->connection = other->connection;
|
|
context->screen_number = other->screen_number;
|
|
context->fbconfigid = other->fbconfigid;
|
|
context->visualid = other->visualid;
|
|
context->glxDrawable = other->glxDrawable;
|
|
context->glxContext = xcb_generate_id(other->connection);
|
|
xcb_glx_create_context(other->connection, context->glxContext, other->visualid, other->screen_number, other->glxContext, 1);
|
|
context->glxContextTag = 0;
|
|
#elif defined(OS_APPLE_MACOS)
|
|
context->nsContext = NULL;
|
|
CGLPixelFormatObj pf = CGLGetPixelFormat(other->cglContext);
|
|
if (CGLCreateContext(pf, other->cglContext, &context->cglContext) != kCGLNoError) {
|
|
return false;
|
|
}
|
|
CGSConnectionID cid;
|
|
CGSWindowID wid;
|
|
CGSSurfaceID sid;
|
|
if (CGLGetSurface(other->cglContext, &cid, &wid, &sid) != kCGLNoError) {
|
|
return false;
|
|
}
|
|
if (CGLSetSurface(context->cglContext, cid, wid, sid) != kCGLNoError) {
|
|
return false;
|
|
}
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
context->display = other->display;
|
|
EGLint configID;
|
|
if (!eglQueryContext(context->display, other->context, EGL_CONFIG_ID, &configID)) {
|
|
Error("eglQueryContext EGL_CONFIG_ID failed: %s", EglErrorString(eglGetError()));
|
|
return false;
|
|
}
|
|
const int MAX_CONFIGS = 1024;
|
|
EGLConfig configs[MAX_CONFIGS];
|
|
EGLint numConfigs = 0;
|
|
EGL(eglGetConfigs(context->display, configs, MAX_CONFIGS, &numConfigs));
|
|
context->config = 0;
|
|
for (int i = 0; i < numConfigs; i++) {
|
|
EGLint value = 0;
|
|
eglGetConfigAttrib(context->display, configs[i], EGL_CONFIG_ID, &value);
|
|
if (value == configID) {
|
|
context->config = configs[i];
|
|
break;
|
|
}
|
|
}
|
|
if (context->config == 0) {
|
|
Error("Failed to find share context config.");
|
|
return false;
|
|
}
|
|
EGLint surfaceType = 0;
|
|
eglGetConfigAttrib(context->display, context->config, EGL_SURFACE_TYPE, &surfaceType);
|
|
|
|
#if defined(OS_ANDROID)
|
|
if ((surfaceType & EGL_PBUFFER_BIT) == 0) {
|
|
Error("Share context config does have EGL_PBUFFER_BIT.");
|
|
return false;
|
|
}
|
|
#endif
|
|
EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, OPENGL_VERSION_MAJOR, EGL_NONE};
|
|
context->context = eglCreateContext(context->display, context->config, other->context, contextAttribs);
|
|
if (context->context == EGL_NO_CONTEXT) {
|
|
Error("eglCreateContext() failed: %s", EglErrorString(eglGetError()));
|
|
return false;
|
|
}
|
|
#if defined(OS_ANDROID)
|
|
const EGLint surfaceAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
|
|
context->tinySurface = eglCreatePbufferSurface(context->display, context->config, surfaceAttribs);
|
|
if (context->tinySurface == EGL_NO_SURFACE) {
|
|
Error("eglCreatePbufferSurface() failed: %s", EglErrorString(eglGetError()));
|
|
eglDestroyContext(context->display, context->context);
|
|
context->context = EGL_NO_CONTEXT;
|
|
return false;
|
|
}
|
|
context->mainSurface = context->tinySurface;
|
|
#endif
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void ksGpuContext_Destroy(ksGpuContext *context) {
|
|
#if defined(OS_WINDOWS)
|
|
if (context->hGLRC) {
|
|
if (!wglMakeCurrent(NULL, NULL)) {
|
|
DWORD error = GetLastError();
|
|
Error("Failed to release context error code (%d).", error);
|
|
}
|
|
|
|
if (!wglDeleteContext(context->hGLRC)) {
|
|
DWORD error = GetLastError();
|
|
Error("Failed to delete context error code (%d).", error);
|
|
}
|
|
context->hGLRC = NULL;
|
|
}
|
|
context->hDC = NULL;
|
|
#elif defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
glXDestroyContext(context->xDisplay, context->glxContext);
|
|
context->xDisplay = NULL;
|
|
context->visualid = 0;
|
|
context->glxFBConfig = NULL;
|
|
context->glxDrawable = 0;
|
|
context->glxContext = NULL;
|
|
#elif defined(OS_LINUX_XCB)
|
|
xcb_glx_destroy_context(context->connection, context->glxContext);
|
|
context->connection = NULL;
|
|
context->screen_number = 0;
|
|
context->fbconfigid = 0;
|
|
context->visualid = 0;
|
|
context->glxDrawable = 0;
|
|
context->glxContext = 0;
|
|
context->glxContextTag = 0;
|
|
#elif defined(OS_APPLE_MACOS)
|
|
CGLSetCurrentContext(NULL);
|
|
if (context->nsContext != NULL) {
|
|
[context->nsContext clearDrawable];
|
|
[context->nsContext release];
|
|
context->nsContext = nil;
|
|
} else {
|
|
CGLDestroyContext(context->cglContext);
|
|
}
|
|
context->cglContext = nil;
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
if (context->display != 0) {
|
|
EGL(eglMakeCurrent(context->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
|
}
|
|
if (context->context != EGL_NO_CONTEXT) {
|
|
EGL(eglDestroyContext(context->display, context->context));
|
|
}
|
|
|
|
#if defined(OS_ANDROID)
|
|
if (context->mainSurface != context->tinySurface) {
|
|
EGL(eglDestroySurface(context->display, context->mainSurface));
|
|
}
|
|
if (context->tinySurface != EGL_NO_SURFACE) {
|
|
EGL(eglDestroySurface(context->display, context->tinySurface));
|
|
}
|
|
context->tinySurface = EGL_NO_SURFACE;
|
|
#elif defined(OS_LINUX_WAYLAND)
|
|
if (context->mainSurface != EGL_NO_SURFACE) {
|
|
EGL(eglDestroySurface(context->display, context->mainSurface));
|
|
}
|
|
#endif
|
|
context->display = 0;
|
|
context->config = 0;
|
|
context->mainSurface = EGL_NO_SURFACE;
|
|
context->context = EGL_NO_CONTEXT;
|
|
#endif
|
|
}
|
|
|
|
void ksGpuContext_WaitIdle(ksGpuContext *context) {
|
|
UNUSED_PARM(context);
|
|
|
|
GL(glFinish());
|
|
}
|
|
|
|
void ksGpuContext_SetCurrent(ksGpuContext *context) {
|
|
#if defined(OS_WINDOWS)
|
|
wglMakeCurrent(context->hDC, context->hGLRC);
|
|
#elif defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
glXMakeCurrent(context->xDisplay, context->glxDrawable, context->glxContext);
|
|
static int firstTime = 1;
|
|
if (firstTime) {
|
|
GlInitExtensions();
|
|
firstTime = 0;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_XCB)
|
|
xcb_glx_make_current_cookie_t glx_make_current_cookie =
|
|
xcb_glx_make_current(context->connection, context->glxDrawable, context->glxContext, 0);
|
|
xcb_glx_make_current_reply_t *glx_make_current_reply =
|
|
xcb_glx_make_current_reply(context->connection, glx_make_current_cookie, NULL);
|
|
context->glxContextTag = glx_make_current_reply->context_tag;
|
|
free(glx_make_current_reply);
|
|
#elif defined(OS_APPLE_MACOS)
|
|
CGLSetCurrentContext(context->cglContext);
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
EGL(eglMakeCurrent(context->display, context->mainSurface, context->mainSurface, context->context));
|
|
#endif
|
|
}
|
|
|
|
void ksGpuContext_UnsetCurrent(ksGpuContext *context) {
|
|
#if defined(OS_WINDOWS)
|
|
wglMakeCurrent(context->hDC, NULL);
|
|
#elif defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
glXMakeCurrent(context->xDisplay, None, NULL);
|
|
#elif defined(OS_LINUX_XCB)
|
|
xcb_glx_make_current(context->connection, 0, 0, 0);
|
|
#elif defined(OS_APPLE_MACOS)
|
|
CGLSetCurrentContext(NULL);
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
EGL(eglMakeCurrent(context->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
|
#endif
|
|
}
|
|
|
|
bool ksGpuContext_CheckCurrent(ksGpuContext *context) {
|
|
#if defined(OS_WINDOWS)
|
|
return (wglGetCurrentContext() == context->hGLRC);
|
|
#elif defined(OS_LINUX_XLIB) || defined(OS_LINUX_XCB_GLX)
|
|
return (glXGetCurrentContext() == context->glxContext);
|
|
#elif defined(OS_LINUX_XCB)
|
|
return true;
|
|
#elif defined(OS_APPLE_MACOS)
|
|
return (CGLGetCurrentContext() == context->cglContext);
|
|
#elif defined(OS_APPLE_IOS)
|
|
return (false); // TODO: pick current context off the UIView
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
return (eglGetCurrentContext() == context->context);
|
|
#endif
|
|
}
|
|
|
|
static void ksGpuContext_GetLimits(ksGpuContext *context, ksGpuLimits *limits) {
|
|
UNUSED_PARM(context);
|
|
|
|
limits->maxPushConstantsSize = 512;
|
|
limits->maxSamples = glGetInteger(GL_MAX_SAMPLES);
|
|
}
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
GPU Window.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
#if defined(OS_WINDOWS)
|
|
|
|
typedef enum {
|
|
KEY_A = 0x41,
|
|
KEY_B = 0x42,
|
|
KEY_C = 0x43,
|
|
KEY_D = 0x44,
|
|
KEY_E = 0x45,
|
|
KEY_F = 0x46,
|
|
KEY_G = 0x47,
|
|
KEY_H = 0x48,
|
|
KEY_I = 0x49,
|
|
KEY_J = 0x4A,
|
|
KEY_K = 0x4B,
|
|
KEY_L = 0x4C,
|
|
KEY_M = 0x4D,
|
|
KEY_N = 0x4E,
|
|
KEY_O = 0x4F,
|
|
KEY_P = 0x50,
|
|
KEY_Q = 0x51,
|
|
KEY_R = 0x52,
|
|
KEY_S = 0x53,
|
|
KEY_T = 0x54,
|
|
KEY_U = 0x55,
|
|
KEY_V = 0x56,
|
|
KEY_W = 0x57,
|
|
KEY_X = 0x58,
|
|
KEY_Y = 0x59,
|
|
KEY_Z = 0x5A,
|
|
KEY_RETURN = VK_RETURN,
|
|
KEY_TAB = VK_TAB,
|
|
KEY_ESCAPE = VK_ESCAPE,
|
|
KEY_SHIFT_LEFT = VK_LSHIFT,
|
|
KEY_CTRL_LEFT = VK_LCONTROL,
|
|
KEY_ALT_LEFT = VK_LMENU,
|
|
KEY_CURSOR_UP = VK_UP,
|
|
KEY_CURSOR_DOWN = VK_DOWN,
|
|
KEY_CURSOR_LEFT = VK_LEFT,
|
|
KEY_CURSOR_RIGHT = VK_RIGHT
|
|
} ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = 0, MOUSE_RIGHT = 1 } ksMouseButton;
|
|
|
|
LRESULT APIENTRY WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|
ksGpuWindow *window = (ksGpuWindow *)GetWindowLongPtrA(hWnd, GWLP_USERDATA);
|
|
|
|
switch (message) {
|
|
case WM_SIZE: {
|
|
if (window != NULL) {
|
|
window->windowWidth = (int)LOWORD(lParam);
|
|
window->windowHeight = (int)HIWORD(lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
case WM_ACTIVATE: {
|
|
if (window != NULL) {
|
|
window->windowActiveState = !HIWORD(wParam);
|
|
}
|
|
return 0;
|
|
}
|
|
case WM_ERASEBKGND: {
|
|
return 0;
|
|
}
|
|
case WM_CLOSE: {
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
case WM_KEYDOWN: {
|
|
if (window != NULL) {
|
|
if ((int)wParam >= 0 && (int)wParam < 256) {
|
|
if ((int)wParam != KEY_SHIFT_LEFT && (int)wParam != KEY_CTRL_LEFT && (int)wParam != KEY_ALT_LEFT &&
|
|
(int)wParam != KEY_CURSOR_UP && (int)wParam != KEY_CURSOR_DOWN && (int)wParam != KEY_CURSOR_LEFT &&
|
|
(int)wParam != KEY_CURSOR_RIGHT) {
|
|
window->input.keyInput[(int)wParam] = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case WM_LBUTTONDOWN: {
|
|
window->input.mouseInput[MOUSE_LEFT] = true;
|
|
window->input.mouseInputX[MOUSE_LEFT] = LOWORD(lParam);
|
|
window->input.mouseInputY[MOUSE_LEFT] = window->windowHeight - HIWORD(lParam);
|
|
break;
|
|
}
|
|
case WM_RBUTTONDOWN: {
|
|
window->input.mouseInput[MOUSE_RIGHT] = true;
|
|
window->input.mouseInputX[MOUSE_RIGHT] = LOWORD(lParam);
|
|
window->input.mouseInputY[MOUSE_RIGHT] = window->windowHeight - HIWORD(lParam);
|
|
break;
|
|
}
|
|
}
|
|
return DefWindowProcA(hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
|
|
if (window->windowFullscreen) {
|
|
ChangeDisplaySettingsA(NULL, 0);
|
|
ShowCursor(TRUE);
|
|
}
|
|
|
|
if (window->hDC) {
|
|
if (!ReleaseDC(window->hWnd, window->hDC)) {
|
|
Error("Failed to release device context.");
|
|
}
|
|
window->hDC = NULL;
|
|
}
|
|
|
|
if (window->hWnd) {
|
|
if (!DestroyWindow(window->hWnd)) {
|
|
Error("Failed to destroy the window.");
|
|
}
|
|
window->hWnd = NULL;
|
|
}
|
|
|
|
if (window->hInstance) {
|
|
if (!UnregisterClassA(APPLICATION_NAME, window->hInstance)) {
|
|
Error("Failed to unregister window class.");
|
|
}
|
|
window->hInstance = NULL;
|
|
}
|
|
}
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, int queueIndex,
|
|
ksGpuSurfaceColorFormat colorFormat, ksGpuSurfaceDepthFormat depthFormat, ksGpuSampleCount sampleCount,
|
|
int width, int height, bool fullscreen) {
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = fullscreen;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->windowActiveState = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
|
|
const LPCSTR displayDevice = NULL;
|
|
|
|
if (window->windowFullscreen) {
|
|
DEVMODEA dmScreenSettings;
|
|
memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
|
|
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
|
|
dmScreenSettings.dmPelsWidth = width;
|
|
dmScreenSettings.dmPelsHeight = height;
|
|
dmScreenSettings.dmBitsPerPel = 32;
|
|
dmScreenSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
|
|
|
|
if (ChangeDisplaySettingsExA(displayDevice, &dmScreenSettings, NULL, CDS_FULLSCREEN, NULL) != DISP_CHANGE_SUCCESSFUL) {
|
|
Error("The requested fullscreen mode is not supported.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DEVMODEA lpDevMode;
|
|
memset(&lpDevMode, 0, sizeof(DEVMODEA));
|
|
lpDevMode.dmSize = sizeof(DEVMODEA);
|
|
lpDevMode.dmDriverExtra = 0;
|
|
|
|
if (EnumDisplaySettingsA(displayDevice, ENUM_CURRENT_SETTINGS, &lpDevMode) != FALSE) {
|
|
window->windowRefreshRate = (float)lpDevMode.dmDisplayFrequency;
|
|
}
|
|
|
|
window->hInstance = GetModuleHandleA(NULL);
|
|
|
|
WNDCLASSA wc;
|
|
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
wc.lpfnWndProc = (WNDPROC)WndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.hInstance = window->hInstance;
|
|
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = NULL;
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = APPLICATION_NAME;
|
|
|
|
if (!RegisterClassA(&wc)) {
|
|
Error("Failed to register window class.");
|
|
return false;
|
|
}
|
|
|
|
DWORD dwExStyle = 0;
|
|
DWORD dwStyle = 0;
|
|
if (window->windowFullscreen) {
|
|
dwExStyle = WS_EX_APPWINDOW;
|
|
dwStyle = WS_POPUP;
|
|
ShowCursor(FALSE);
|
|
} else {
|
|
// Fixed size window.
|
|
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
|
|
dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
|
}
|
|
|
|
RECT windowRect;
|
|
windowRect.left = (long)0;
|
|
windowRect.right = (long)width;
|
|
windowRect.top = (long)0;
|
|
windowRect.bottom = (long)height;
|
|
|
|
AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);
|
|
|
|
if (!window->windowFullscreen) {
|
|
RECT desktopRect;
|
|
GetWindowRect(GetDesktopWindow(), &desktopRect);
|
|
|
|
const int offsetX = (desktopRect.right - (windowRect.right - windowRect.left)) / 2;
|
|
const int offsetY = (desktopRect.bottom - (windowRect.bottom - windowRect.top)) / 2;
|
|
|
|
windowRect.left += offsetX;
|
|
windowRect.right += offsetX;
|
|
windowRect.top += offsetY;
|
|
windowRect.bottom += offsetY;
|
|
}
|
|
|
|
window->hWnd = CreateWindowExA(dwExStyle, // Extended style for the window
|
|
APPLICATION_NAME, // Class name
|
|
WINDOW_TITLE, // Window title
|
|
dwStyle | // Defined window style
|
|
WS_CLIPSIBLINGS | // Required window style
|
|
WS_CLIPCHILDREN, // Required window style
|
|
windowRect.left, // Window X position
|
|
windowRect.top, // Window Y position
|
|
windowRect.right - windowRect.left, // Window width
|
|
windowRect.bottom - windowRect.top, // Window height
|
|
NULL, // No parent window
|
|
NULL, // No menu
|
|
window->hInstance, // Instance
|
|
NULL); // No WM_CREATE parameter
|
|
if (!window->hWnd) {
|
|
ksGpuWindow_Destroy(window);
|
|
Error("Failed to create window.");
|
|
return false;
|
|
}
|
|
|
|
SetWindowLongPtrA(window->hWnd, GWLP_USERDATA, (LONG_PTR)window);
|
|
|
|
window->hDC = GetDC(window->hWnd);
|
|
if (!window->hDC) {
|
|
ksGpuWindow_Destroy(window);
|
|
Error("Failed to acquire device context.");
|
|
return false;
|
|
}
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
window->hInstance, window->hDC);
|
|
ksGpuContext_SetCurrent(&window->context);
|
|
|
|
ShowWindow(window->hWnd, SW_SHOW);
|
|
SetForegroundWindow(window->hWnd);
|
|
SetFocus(window->hWnd);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ksGpuWindow_SupportedResolution(const int width, const int height) {
|
|
DEVMODE dm = {0};
|
|
dm.dmSize = sizeof(dm);
|
|
for (int modeIndex = 0; EnumDisplaySettings(NULL, modeIndex, &dm) != 0; modeIndex++) {
|
|
if (dm.dmPelsWidth == (DWORD)width && dm.dmPelsHeight == (DWORD)height) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ksGpuWindow_Exit(ksGpuWindow *window) { window->windowExit = true; }
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
MSG msg;
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) {
|
|
if (msg.message == WM_QUIT) {
|
|
window->windowExit = true;
|
|
} else {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
window->input.keyInput[KEY_SHIFT_LEFT] = GetAsyncKeyState(KEY_SHIFT_LEFT) != 0;
|
|
window->input.keyInput[KEY_CTRL_LEFT] = GetAsyncKeyState(KEY_CTRL_LEFT) != 0;
|
|
window->input.keyInput[KEY_ALT_LEFT] = GetAsyncKeyState(KEY_ALT_LEFT) != 0;
|
|
window->input.keyInput[KEY_CURSOR_UP] = GetAsyncKeyState(KEY_CURSOR_UP) != 0;
|
|
window->input.keyInput[KEY_CURSOR_DOWN] = GetAsyncKeyState(KEY_CURSOR_DOWN) != 0;
|
|
window->input.keyInput[KEY_CURSOR_LEFT] = GetAsyncKeyState(KEY_CURSOR_LEFT) != 0;
|
|
window->input.keyInput[KEY_CURSOR_RIGHT] = GetAsyncKeyState(KEY_CURSOR_RIGHT) != 0;
|
|
|
|
if (window->windowExit) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
if (window->windowActiveState != window->windowActive) {
|
|
window->windowActive = window->windowActiveState;
|
|
return (window->windowActiveState) ? KS_GPU_WINDOW_EVENT_ACTIVATED : KS_GPU_WINDOW_EVENT_DEACTIVATED;
|
|
}
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_XLIB)
|
|
|
|
typedef enum // keysym.h
|
|
{ KEY_A = XK_a,
|
|
KEY_B = XK_b,
|
|
KEY_C = XK_c,
|
|
KEY_D = XK_d,
|
|
KEY_E = XK_e,
|
|
KEY_F = XK_f,
|
|
KEY_G = XK_g,
|
|
KEY_H = XK_h,
|
|
KEY_I = XK_i,
|
|
KEY_J = XK_j,
|
|
KEY_K = XK_k,
|
|
KEY_L = XK_l,
|
|
KEY_M = XK_m,
|
|
KEY_N = XK_n,
|
|
KEY_O = XK_o,
|
|
KEY_P = XK_p,
|
|
KEY_Q = XK_q,
|
|
KEY_R = XK_r,
|
|
KEY_S = XK_s,
|
|
KEY_T = XK_t,
|
|
KEY_U = XK_u,
|
|
KEY_V = XK_v,
|
|
KEY_W = XK_w,
|
|
KEY_X = XK_x,
|
|
KEY_Y = XK_y,
|
|
KEY_Z = XK_z,
|
|
KEY_RETURN = (XK_Return & 0xFF),
|
|
KEY_TAB = (XK_Tab & 0xFF),
|
|
KEY_ESCAPE = (XK_Escape & 0xFF),
|
|
KEY_SHIFT_LEFT = (XK_Shift_L & 0xFF),
|
|
KEY_CTRL_LEFT = (XK_Control_L & 0xFF),
|
|
KEY_ALT_LEFT = (XK_Alt_L & 0xFF),
|
|
KEY_CURSOR_UP = (XK_Up & 0xFF),
|
|
KEY_CURSOR_DOWN = (XK_Down & 0xFF),
|
|
KEY_CURSOR_LEFT = (XK_Left & 0xFF),
|
|
KEY_CURSOR_RIGHT = (XK_Right & 0xFF) } ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = Button1, MOUSE_RIGHT = Button2 } ksMouseButton;
|
|
|
|
/*
|
|
Change video mode using the XFree86-VidMode X extension.
|
|
|
|
While the XFree86-VidMode X extension should be superseded by the XRandR X extension,
|
|
this still appears to be the most reliable way to change video modes for a single
|
|
monitor configuration.
|
|
*/
|
|
static bool ChangeVideoMode_XF86VidMode(Display *xDisplay, int xScreen, int *currentWidth, int *currentHeight,
|
|
float *currentRefreshRate, int *desiredWidth, int *desiredHeight,
|
|
float *desiredRefreshRate) {
|
|
int videoModeCount;
|
|
XF86VidModeModeInfo **videoModeInfos;
|
|
|
|
XF86VidModeGetAllModeLines(xDisplay, xScreen, &videoModeCount, &videoModeInfos);
|
|
|
|
if (currentWidth != NULL && currentHeight != NULL && currentRefreshRate != NULL) {
|
|
XF86VidModeModeInfo *mode = videoModeInfos[0];
|
|
*currentWidth = mode->hdisplay;
|
|
*currentHeight = mode->vdisplay;
|
|
*currentRefreshRate = (mode->dotclock * 1000.0f) / (mode->htotal * mode->vtotal);
|
|
}
|
|
|
|
if (desiredWidth != NULL && desiredHeight != NULL && desiredRefreshRate != NULL) {
|
|
XF86VidModeModeInfo *bestMode = NULL;
|
|
int bestModeWidth = 0;
|
|
int bestModeHeight = 0;
|
|
float bestModeRefreshRate = 0.0f;
|
|
int bestSizeError = 0x7FFFFFFF;
|
|
float bestRefreshRateError = 1e6f;
|
|
for (int j = 0; j < videoModeCount; j++) {
|
|
XF86VidModeModeInfo *mode = videoModeInfos[j];
|
|
const int modeWidth = mode->hdisplay;
|
|
const int modeHeight = mode->vdisplay;
|
|
const float modeRefreshRate = (mode->dotclock * 1000.0f) / (mode->htotal * mode->vtotal);
|
|
|
|
const int dw = modeWidth - *desiredWidth;
|
|
const int dh = modeHeight - *desiredHeight;
|
|
const int sizeError = dw * dw + dh * dh;
|
|
const float refreshRateError = fabsf(modeRefreshRate - *desiredRefreshRate);
|
|
if (sizeError < bestSizeError || (sizeError == bestSizeError && refreshRateError < bestRefreshRateError)) {
|
|
bestSizeError = sizeError;
|
|
bestRefreshRateError = refreshRateError;
|
|
bestMode = mode;
|
|
bestModeWidth = modeWidth;
|
|
bestModeHeight = modeHeight;
|
|
bestModeRefreshRate = modeRefreshRate;
|
|
}
|
|
}
|
|
|
|
XF86VidModeSwitchToMode(xDisplay, xScreen, bestMode);
|
|
XF86VidModeSetViewPort(xDisplay, xScreen, 0, 0);
|
|
|
|
*desiredWidth = bestModeWidth;
|
|
*desiredHeight = bestModeHeight;
|
|
*desiredRefreshRate = bestModeRefreshRate;
|
|
}
|
|
|
|
for (int i = 0; i < videoModeCount; i++) {
|
|
if (videoModeInfos[i]->privsize > 0) {
|
|
XFree(videoModeInfos[i]->private);
|
|
}
|
|
}
|
|
XFree(videoModeInfos);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Change video mode using the XRandR X extension version 1.1
|
|
|
|
This does not work using NVIDIA drivers because the NVIDIA drivers by default dynamically
|
|
configure TwinView, known as DynamicTwinView. When DynamicTwinView is enabled (the default),
|
|
the refresh rate of a mode reported through XRandR is not the actual refresh rate, but
|
|
instead is an unique number such that each MetaMode has a different value. This is to
|
|
guarantee that MetaModes can be uniquely identified by XRandR.
|
|
|
|
To get XRandR to report accurate refresh rates, DynamicTwinView needs to be disabled, but
|
|
then NV-CONTROL clients, such as nvidia-settings, will not be able to dynamically manipulate
|
|
the X screen's MetaModes.
|
|
*/
|
|
static bool ChangeVideoMode_XRandR_1_1(Display *xDisplay, Window xWindow, int *currentWidth, int *currentHeight,
|
|
float *currentRefreshRate, int *desiredWidth, int *desiredHeight,
|
|
float *desiredRefreshRate) {
|
|
int major_version;
|
|
int minor_version;
|
|
XRRQueryVersion(xDisplay, &major_version, &minor_version);
|
|
|
|
XRRScreenConfiguration *screenInfo = XRRGetScreenInfo(xDisplay, xWindow);
|
|
if (screenInfo == NULL) {
|
|
Error("Cannot get screen info.");
|
|
return false;
|
|
}
|
|
|
|
if (currentWidth != NULL && currentHeight != NULL && currentRefreshRate != NULL) {
|
|
XRRScreenConfiguration *screenInfo = XRRGetScreenInfo(xDisplay, xWindow);
|
|
|
|
Rotation rotation;
|
|
int size_index = XRRConfigCurrentConfiguration(screenInfo, &rotation);
|
|
|
|
int nsizes;
|
|
XRRScreenSize *sizes = XRRConfigSizes(screenInfo, &nsizes);
|
|
|
|
*currentWidth = sizes[size_index].width;
|
|
*currentHeight = sizes[size_index].height;
|
|
*currentRefreshRate = XRRConfigCurrentRate(screenInfo);
|
|
}
|
|
|
|
if (desiredWidth != NULL && desiredHeight != NULL && desiredRefreshRate != NULL) {
|
|
int nsizes = 0;
|
|
XRRScreenSize *sizes = XRRConfigSizes(screenInfo, &nsizes);
|
|
|
|
int size_index = -1;
|
|
int bestSizeError = 0x7FFFFFFF;
|
|
for (int i = 0; i < nsizes; i++) {
|
|
const int dw = sizes[i].width - *desiredWidth;
|
|
const int dh = sizes[i].height - *desiredHeight;
|
|
const int error = dw * dw + dh * dh;
|
|
if (error < bestSizeError) {
|
|
bestSizeError = error;
|
|
size_index = i;
|
|
}
|
|
}
|
|
if (size_index == -1) {
|
|
Error("%dx%d resolution not available.", *desiredWidth, *desiredHeight);
|
|
XRRFreeScreenConfigInfo(screenInfo);
|
|
return false;
|
|
}
|
|
|
|
int nrates = 0;
|
|
short *rates = XRRConfigRates(screenInfo, size_index, &nrates);
|
|
|
|
int rate_index = -1;
|
|
float bestRateError = 1e6f;
|
|
for (int i = 0; i < nrates; i++) {
|
|
const float error = fabsf(rates[i] - *desiredRefreshRate);
|
|
if (error < bestRateError) {
|
|
bestRateError = error;
|
|
rate_index = i;
|
|
}
|
|
}
|
|
|
|
*desiredWidth = sizes[size_index].width;
|
|
*desiredHeight = sizes[size_index].height;
|
|
*desiredRefreshRate = rates[rate_index];
|
|
|
|
XSelectInput(xDisplay, xWindow, StructureNotifyMask);
|
|
XRRSelectInput(xDisplay, xWindow, RRScreenChangeNotifyMask);
|
|
|
|
Rotation rotation = 1;
|
|
int reflection = 0;
|
|
|
|
Status status = XRRSetScreenConfigAndRate(xDisplay, screenInfo, xWindow, (SizeID)size_index,
|
|
(Rotation)(rotation | reflection), rates[rate_index], CurrentTime);
|
|
|
|
if (status != RRSetConfigSuccess) {
|
|
Error("Failed to change resolution to %dx%d", *desiredWidth, *desiredHeight);
|
|
XRRFreeScreenConfigInfo(screenInfo);
|
|
return false;
|
|
}
|
|
|
|
int eventbase;
|
|
int errorbase;
|
|
XRRQueryExtension(xDisplay, &eventbase, &errorbase);
|
|
|
|
bool receivedScreenChangeNotify = false;
|
|
bool receivedConfigNotify = false;
|
|
while (1) {
|
|
XEvent event;
|
|
XNextEvent(xDisplay, (XEvent *)&event);
|
|
XRRUpdateConfiguration(&event);
|
|
if (event.type - eventbase == RRScreenChangeNotify) {
|
|
receivedScreenChangeNotify = true;
|
|
} else if (event.type == ConfigureNotify) {
|
|
receivedConfigNotify = true;
|
|
}
|
|
if (receivedScreenChangeNotify && receivedConfigNotify) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
XRRFreeScreenConfigInfo(screenInfo);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Change video mode using the XRandR X extension version 1.2
|
|
|
|
The following code does not necessarily work out of the box, because on
|
|
some configurations the modes list returned by XRRGetScreenResources()
|
|
is populated with nothing other than the maximum display resolution,
|
|
even though XF86VidModeGetAllModeLines() and XRRConfigSizes() *will*
|
|
list all resolutions for the same display.
|
|
|
|
The user can manually add new modes from the command-line using the
|
|
xrandr utility:
|
|
|
|
xrandr --newmode <modeline>
|
|
|
|
Where <modeline> is generated with a utility that implements either
|
|
the General Timing Formula (GTF) or the Coordinated Video Timing (CVT)
|
|
standard put forth by the Video Electronics Standards Association (VESA):
|
|
|
|
gft <width> <height> <Hz> // http://gtf.sourceforge.net/
|
|
cvt <width> <height> <Hz> // http://www.uruk.org/~erich/projects/cvt/
|
|
|
|
Alternatively, new modes can be added in code using XRRCreateMode().
|
|
However, this requires calculating all the timing information in code
|
|
because there is no standard library that implements the GTF or CVT.
|
|
*/
|
|
static bool ChangeVideoMode_XRandR_1_2(Display *xDisplay, Window xWindow, int *currentWidth, int *currentHeight,
|
|
float *currentRefreshRate, int *desiredWidth, int *desiredHeight,
|
|
float *desiredRefreshRate) {
|
|
int major_version;
|
|
int minor_version;
|
|
XRRQueryVersion(xDisplay, &major_version, &minor_version);
|
|
|
|
/*
|
|
Screen - virtual screenspace which may be covered by multiple CRTCs
|
|
CRTC - display controller
|
|
Output - display/monitor connected to a CRTC
|
|
Clones - outputs that are simultaneously connected to the same CRTC
|
|
*/
|
|
|
|
const int PRIMARY_CRTC_INDEX = 0;
|
|
const int PRIMARY_OUTPUT_INDEX = 0;
|
|
|
|
XRRScreenResources *screenResources = XRRGetScreenResources(xDisplay, xWindow);
|
|
XRRCrtcInfo *primaryCrtcInfo = XRRGetCrtcInfo(xDisplay, screenResources, screenResources->crtcs[PRIMARY_CRTC_INDEX]);
|
|
XRROutputInfo *primaryOutputInfo = XRRGetOutputInfo(xDisplay, screenResources, primaryCrtcInfo->outputs[PRIMARY_OUTPUT_INDEX]);
|
|
|
|
if (currentWidth != NULL && currentHeight != NULL && currentRefreshRate != NULL) {
|
|
for (int i = 0; i < screenResources->nmode; i++) {
|
|
const XRRModeInfo *modeInfo = &screenResources->modes[i];
|
|
if (modeInfo->id == primaryCrtcInfo->mode) {
|
|
*currentWidth = modeInfo->width;
|
|
*currentHeight = modeInfo->height;
|
|
*currentRefreshRate = modeInfo->dotClock / ((float)modeInfo->hTotal * (float)modeInfo->vTotal);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (desiredWidth != NULL && desiredHeight != NULL && desiredRefreshRate != NULL) {
|
|
RRMode bestMode = 0;
|
|
int bestModeWidth = 0;
|
|
int bestModeHeight = 0;
|
|
float bestModeRefreshRate = 0.0f;
|
|
int bestSizeError = 0x7FFFFFFF;
|
|
float bestRefreshRateError = 1e6f;
|
|
|
|
for (int i = 0; i < screenResources->nmode; i++) {
|
|
const XRRModeInfo *modeInfo = &screenResources->modes[i];
|
|
|
|
if (modeInfo->modeFlags & RR_Interlace) {
|
|
continue;
|
|
}
|
|
|
|
bool validOutputMode = false;
|
|
for (int j = 0; j < primaryOutputInfo->nmode; j++) {
|
|
if (modeInfo->id == primaryOutputInfo->modes[j]) {
|
|
validOutputMode = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!validOutputMode) {
|
|
continue;
|
|
}
|
|
|
|
const int modeWidth = modeInfo->width;
|
|
const int modeHeight = modeInfo->height;
|
|
const float modeRefreshRate = modeInfo->dotClock / ((float)modeInfo->hTotal * (float)modeInfo->vTotal);
|
|
|
|
const int dw = modeWidth - *desiredWidth;
|
|
const int dh = modeHeight - *desiredHeight;
|
|
const int sizeError = dw * dw + dh * dh;
|
|
const float refreshRateError = fabsf(modeRefreshRate - *desiredRefreshRate);
|
|
if (sizeError < bestSizeError || (sizeError == bestSizeError && refreshRateError < bestRefreshRateError)) {
|
|
bestSizeError = sizeError;
|
|
bestRefreshRateError = refreshRateError;
|
|
bestMode = modeInfo->id;
|
|
bestModeWidth = modeWidth;
|
|
bestModeHeight = modeHeight;
|
|
bestModeRefreshRate = modeRefreshRate;
|
|
}
|
|
}
|
|
|
|
XRRSetCrtcConfig(xDisplay, screenResources, primaryOutputInfo->crtc, CurrentTime, primaryCrtcInfo->x, primaryCrtcInfo->y,
|
|
bestMode, primaryCrtcInfo->rotation, primaryCrtcInfo->outputs, primaryCrtcInfo->noutput);
|
|
|
|
*desiredWidth = bestModeWidth;
|
|
*desiredHeight = bestModeHeight;
|
|
*desiredRefreshRate = bestModeRefreshRate;
|
|
}
|
|
|
|
XRRFreeOutputInfo(primaryOutputInfo);
|
|
XRRFreeCrtcInfo(primaryCrtcInfo);
|
|
XRRFreeScreenResources(screenResources);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
|
|
if (window->windowFullscreen) {
|
|
ChangeVideoMode_XF86VidMode(window->xDisplay, window->xScreen, NULL, NULL, NULL, &window->desktopWidth,
|
|
&window->desktopHeight, &window->desktopRefreshRate);
|
|
|
|
XUngrabPointer(window->xDisplay, CurrentTime);
|
|
XUngrabKeyboard(window->xDisplay, CurrentTime);
|
|
}
|
|
|
|
if (window->xWindow) {
|
|
XUnmapWindow(window->xDisplay, window->xWindow);
|
|
XDestroyWindow(window->xDisplay, window->xWindow);
|
|
window->xWindow = 0;
|
|
}
|
|
|
|
if (window->xColormap) {
|
|
XFreeColormap(window->xDisplay, window->xColormap);
|
|
window->xColormap = 0;
|
|
}
|
|
|
|
if (window->xVisual) {
|
|
XFree(window->xVisual);
|
|
window->xVisual = NULL;
|
|
}
|
|
|
|
XFlush(window->xDisplay);
|
|
XCloseDisplay(window->xDisplay);
|
|
window->xDisplay = NULL;
|
|
}
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, const int width, const int height, const bool fullscreen) {
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = fullscreen;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
|
|
const char *displayName = NULL;
|
|
window->xDisplay = XOpenDisplay(displayName);
|
|
if (!window->xDisplay) {
|
|
Error("Unable to open X Display.");
|
|
return false;
|
|
}
|
|
|
|
window->xScreen = XDefaultScreen(window->xDisplay);
|
|
window->xRoot = XRootWindow(window->xDisplay, window->xScreen);
|
|
|
|
if (window->windowFullscreen) {
|
|
ChangeVideoMode_XF86VidMode(window->xDisplay, window->xScreen, &window->desktopWidth, &window->desktopHeight,
|
|
&window->desktopRefreshRate, &window->windowWidth, &window->windowHeight,
|
|
&window->windowRefreshRate);
|
|
} else {
|
|
ChangeVideoMode_XF86VidMode(window->xDisplay, window->xScreen, &window->desktopWidth, &window->desktopHeight,
|
|
&window->desktopRefreshRate, NULL, NULL, NULL);
|
|
window->windowRefreshRate = window->desktopRefreshRate;
|
|
}
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
window->xDisplay, window->xScreen);
|
|
|
|
window->xVisual = glXGetVisualFromFBConfig(window->xDisplay, window->context.glxFBConfig);
|
|
if (window->xVisual == NULL) {
|
|
Error("Failed to retrieve visual for framebuffer config.");
|
|
ksGpuWindow_Destroy(window);
|
|
return false;
|
|
}
|
|
|
|
window->xColormap = XCreateColormap(window->xDisplay, window->xRoot, window->xVisual->visual, AllocNone);
|
|
|
|
const unsigned long wamask = CWColormap | CWEventMask | (window->windowFullscreen ? 0 : CWBorderPixel);
|
|
|
|
XSetWindowAttributes wa;
|
|
memset(&wa, 0, sizeof(wa));
|
|
wa.colormap = window->xColormap;
|
|
wa.border_pixel = 0;
|
|
wa.event_mask = StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask | KeyPressMask | KeyReleaseMask |
|
|
ButtonPressMask | ButtonReleaseMask | FocusChangeMask | ExposureMask | VisibilityChangeMask | EnterWindowMask |
|
|
LeaveWindowMask;
|
|
|
|
window->xWindow = XCreateWindow(window->xDisplay, // Display * display
|
|
window->xRoot, // Window parent
|
|
0, // int x
|
|
0, // int y
|
|
window->windowWidth, // unsigned int width
|
|
window->windowHeight, // unsigned int height
|
|
0, // unsigned int border_width
|
|
window->xVisual->depth, // int depth
|
|
InputOutput, // unsigned int class
|
|
window->xVisual->visual, // Visual * visual
|
|
wamask, // unsigned long valuemask
|
|
&wa); // XSetWindowAttributes * attributes
|
|
|
|
if (!window->xWindow) {
|
|
Error("Failed to create window.");
|
|
ksGpuWindow_Destroy(window);
|
|
return false;
|
|
}
|
|
|
|
// Change the window title.
|
|
Atom _NET_WM_NAME = XInternAtom(window->xDisplay, "_NET_WM_NAME", False);
|
|
XChangeProperty(window->xDisplay, window->xWindow, _NET_WM_NAME, XA_STRING, 8, PropModeReplace,
|
|
(const unsigned char *)WINDOW_TITLE, strlen(WINDOW_TITLE));
|
|
|
|
if (window->windowFullscreen) {
|
|
// Bypass the compositor in fullscreen mode.
|
|
const unsigned long bypass = 1;
|
|
Atom _NET_WM_BYPASS_COMPOSITOR = XInternAtom(window->xDisplay, "_NET_WM_BYPASS_COMPOSITOR", False);
|
|
XChangeProperty(window->xDisplay, window->xWindow, _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, PropModeReplace,
|
|
(const unsigned char *)&bypass, 1);
|
|
|
|
// Completely disassociate window from window manager.
|
|
XSetWindowAttributes attributes;
|
|
attributes.override_redirect = True;
|
|
XChangeWindowAttributes(window->xDisplay, window->xWindow, CWOverrideRedirect, &attributes);
|
|
|
|
// Make the window visible.
|
|
XMapRaised(window->xDisplay, window->xWindow);
|
|
XMoveResizeWindow(window->xDisplay, window->xWindow, 0, 0, window->windowWidth, window->windowHeight);
|
|
XFlush(window->xDisplay);
|
|
|
|
// Grab mouse and keyboard input now that the window is disassociated from the window manager.
|
|
XGrabPointer(window->xDisplay, window->xWindow, True, 0, GrabModeAsync, GrabModeAsync, window->xWindow, 0L, CurrentTime);
|
|
XGrabKeyboard(window->xDisplay, window->xWindow, True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
} else {
|
|
// Make the window fixed size.
|
|
XSizeHints *hints = XAllocSizeHints();
|
|
hints->flags = (PMinSize | PMaxSize);
|
|
hints->min_width = window->windowWidth;
|
|
hints->max_width = window->windowWidth;
|
|
hints->min_height = window->windowHeight;
|
|
hints->max_height = window->windowHeight;
|
|
XSetWMNormalHints(window->xDisplay, window->xWindow, hints);
|
|
XFree(hints);
|
|
|
|
// First map the window and then center the window on the screen.
|
|
XMapRaised(window->xDisplay, window->xWindow);
|
|
const int x = (window->desktopWidth - window->windowWidth) / 2;
|
|
const int y = (window->desktopHeight - window->windowHeight) / 2;
|
|
XMoveResizeWindow(window->xDisplay, window->xWindow, x, y, window->windowWidth, window->windowHeight);
|
|
XFlush(window->xDisplay);
|
|
}
|
|
|
|
window->context.glxDrawable = window->xWindow;
|
|
|
|
ksGpuContext_SetCurrent(&window->context);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ksGpuWindow_SupportedResolution(const int width, const int height) {
|
|
UNUSED_PARM(width);
|
|
UNUSED_PARM(height);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Exit(ksGpuWindow *window) { window->windowExit = true; }
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
int count = XPending(window->xDisplay);
|
|
for (int i = 0; i < count; i++) {
|
|
XEvent event;
|
|
XNextEvent(window->xDisplay, &event);
|
|
|
|
switch (event.type) {
|
|
case KeyPress: {
|
|
KeySym key = XLookupKeysym(&event.xkey, 0);
|
|
if (key < 256 || key == XK_Escape) {
|
|
window->input.keyInput[key & 255] = true;
|
|
}
|
|
break;
|
|
}
|
|
case KeyRelease: {
|
|
KeySym key = XLookupKeysym(&event.xkey, 0);
|
|
if (key == XK_Escape) {
|
|
exit(0);
|
|
}
|
|
break;
|
|
}
|
|
case ButtonPress: {
|
|
window->input.mouseInput[event.xbutton.button] = true;
|
|
window->input.mouseInputX[event.xbutton.button] = event.xbutton.x;
|
|
window->input.mouseInputY[event.xbutton.button] = event.xbutton.y;
|
|
}
|
|
case ButtonRelease: {
|
|
break;
|
|
}
|
|
// StructureNotifyMask
|
|
case ConfigureNotify:
|
|
case MapNotify:
|
|
case UnmapNotify:
|
|
case DestroyNotify:
|
|
// PropertyChangeMask
|
|
case PropertyNotify:
|
|
// ResizeRedirectMask
|
|
case ResizeRequest:
|
|
// EnterWindowMask | LeaveWindowMask
|
|
case EnterNotify:
|
|
case LeaveNotify:
|
|
// FocusChangeMask
|
|
case FocusIn:
|
|
case FocusOut:
|
|
// ExposureMask
|
|
case Expose:
|
|
// VisibilityChangeMask
|
|
case VisibilityNotify:
|
|
|
|
case GenericEvent:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (window->windowExit) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
|
|
if (window->windowActive == false) {
|
|
window->windowActive = true;
|
|
return KS_GPU_WINDOW_EVENT_ACTIVATED;
|
|
}
|
|
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_XCB) || defined(OS_LINUX_XCB_GLX)
|
|
|
|
typedef enum // keysym.h
|
|
{ KEY_A = XK_a,
|
|
KEY_B = XK_b,
|
|
KEY_C = XK_c,
|
|
KEY_D = XK_d,
|
|
KEY_E = XK_e,
|
|
KEY_F = XK_f,
|
|
KEY_G = XK_g,
|
|
KEY_H = XK_h,
|
|
KEY_I = XK_i,
|
|
KEY_J = XK_j,
|
|
KEY_K = XK_k,
|
|
KEY_L = XK_l,
|
|
KEY_M = XK_m,
|
|
KEY_N = XK_n,
|
|
KEY_O = XK_o,
|
|
KEY_P = XK_p,
|
|
KEY_Q = XK_q,
|
|
KEY_R = XK_r,
|
|
KEY_S = XK_s,
|
|
KEY_T = XK_t,
|
|
KEY_U = XK_u,
|
|
KEY_V = XK_v,
|
|
KEY_W = XK_w,
|
|
KEY_X = XK_x,
|
|
KEY_Y = XK_y,
|
|
KEY_Z = XK_z,
|
|
KEY_RETURN = (XK_Return & 0xFF),
|
|
KEY_TAB = (XK_Tab & 0xFF),
|
|
KEY_ESCAPE = (XK_Escape & 0xFF),
|
|
KEY_SHIFT_LEFT = (XK_Shift_L & 0xFF),
|
|
KEY_CTRL_LEFT = (XK_Control_L & 0xFF),
|
|
KEY_ALT_LEFT = (XK_Alt_L & 0xFF),
|
|
KEY_CURSOR_UP = (XK_Up & 0xFF),
|
|
KEY_CURSOR_DOWN = (XK_Down & 0xFF),
|
|
KEY_CURSOR_LEFT = (XK_Left & 0xFF),
|
|
KEY_CURSOR_RIGHT = (XK_Right & 0xFF) } ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = 0, MOUSE_RIGHT = 1 } ksMouseButton;
|
|
|
|
typedef enum {
|
|
XCB_SIZE_HINT_US_POSITION = 1 << 0,
|
|
XCB_SIZE_HINT_US_SIZE = 1 << 1,
|
|
XCB_SIZE_HINT_P_POSITION = 1 << 2,
|
|
XCB_SIZE_HINT_P_SIZE = 1 << 3,
|
|
XCB_SIZE_HINT_P_MIN_SIZE = 1 << 4,
|
|
XCB_SIZE_HINT_P_MAX_SIZE = 1 << 5,
|
|
XCB_SIZE_HINT_P_RESIZE_INC = 1 << 6,
|
|
XCB_SIZE_HINT_P_ASPECT = 1 << 7,
|
|
XCB_SIZE_HINT_BASE_SIZE = 1 << 8,
|
|
XCB_SIZE_HINT_P_WIN_GRAVITY = 1 << 9
|
|
} xcb_size_hints_flags_t;
|
|
|
|
static const int _NET_WM_STATE_ADD = 1; // add/set property
|
|
|
|
/*
|
|
Change video mode using the RandR X extension version 1.4
|
|
|
|
The following code does not necessarily work out of the box, because on
|
|
some configurations the modes list returned by XRRGetScreenResources()
|
|
is populated with nothing other than the maximum display resolution,
|
|
even though XF86VidModeGetAllModeLines() and XRRConfigSizes() *will*
|
|
list all resolutions for the same display.
|
|
|
|
The user can manually add new modes from the command-line using the
|
|
xrandr utility:
|
|
|
|
xrandr --newmode <modeline>
|
|
|
|
Where <modeline> is generated with a utility that implements either
|
|
the General Timing Formula (GTF) or the Coordinated Video Timing (CVT)
|
|
standard put forth by the Video Electronics Standards Association (VESA):
|
|
|
|
gft <width> <height> <Hz> // http://gtf.sourceforge.net/
|
|
cvt <width> <height> <Hz> // http://www.uruk.org/~erich/projects/cvt/
|
|
|
|
Alternatively, new modes can be added in code using XRRCreateMode().
|
|
However, this requires calculating all the timing information in code
|
|
because there is no standard library that implements the GTF or CVT.
|
|
*/
|
|
static bool ChangeVideoMode_XcbRandR_1_4(xcb_connection_t *connection, xcb_screen_t *screen, int *currentWidth, int *currentHeight,
|
|
float *currentRefreshRate, int *desiredWidth, int *desiredHeight,
|
|
float *desiredRefreshRate) {
|
|
/*
|
|
Screen - virtual screenspace which may be covered by multiple CRTCs
|
|
CRTC - display controller
|
|
Output - display/monitor connected to a CRTC
|
|
Clones - outputs that are simultaneously connected to the same CRTC
|
|
*/
|
|
|
|
xcb_randr_get_screen_resources_cookie_t screen_resources_cookie = xcb_randr_get_screen_resources(connection, screen->root);
|
|
xcb_randr_get_screen_resources_reply_t *screen_resources_reply =
|
|
xcb_randr_get_screen_resources_reply(connection, screen_resources_cookie, 0);
|
|
if (screen_resources_reply == NULL) {
|
|
return false;
|
|
}
|
|
|
|
xcb_randr_mode_info_t *mode_info = xcb_randr_get_screen_resources_modes(screen_resources_reply);
|
|
const int modes_length = xcb_randr_get_screen_resources_modes_length(screen_resources_reply);
|
|
assert(modes_length > 0);
|
|
|
|
xcb_randr_crtc_t *crtcs = xcb_randr_get_screen_resources_crtcs(screen_resources_reply);
|
|
const int crtcs_length = xcb_randr_get_screen_resources_crtcs_length(screen_resources_reply);
|
|
assert(crtcs_length > 0);
|
|
UNUSED_PARM(crtcs_length);
|
|
|
|
const int PRIMARY_CRTC_INDEX = 0;
|
|
const int PRIMARY_OUTPUT_INDEX = 0;
|
|
|
|
xcb_randr_get_crtc_info_cookie_t primary_crtc_info_cookie = xcb_randr_get_crtc_info(connection, crtcs[PRIMARY_CRTC_INDEX], 0);
|
|
xcb_randr_get_crtc_info_reply_t *primary_crtc_info_reply =
|
|
xcb_randr_get_crtc_info_reply(connection, primary_crtc_info_cookie, NULL);
|
|
|
|
xcb_randr_output_t *crtc_outputs = xcb_randr_get_crtc_info_outputs(primary_crtc_info_reply);
|
|
|
|
xcb_randr_get_output_info_cookie_t primary_output_info_cookie =
|
|
xcb_randr_get_output_info(connection, crtc_outputs[PRIMARY_OUTPUT_INDEX], 0);
|
|
xcb_randr_get_output_info_reply_t *primary_output_info_reply =
|
|
xcb_randr_get_output_info_reply(connection, primary_output_info_cookie, NULL);
|
|
|
|
if (currentWidth != NULL && currentHeight != NULL && currentRefreshRate != NULL) {
|
|
for (int i = 0; i < modes_length; i++) {
|
|
if (mode_info[i].id == primary_crtc_info_reply->mode) {
|
|
*currentWidth = mode_info[i].width;
|
|
*currentHeight = mode_info[i].height;
|
|
*currentRefreshRate = mode_info[i].dot_clock / ((float)mode_info[i].htotal * (float)mode_info[i].vtotal);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (desiredWidth != NULL && desiredHeight != NULL && desiredRefreshRate != NULL) {
|
|
xcb_randr_mode_t bestMode = 0;
|
|
int bestModeWidth = 0;
|
|
int bestModeHeight = 0;
|
|
float bestModeRefreshRate = 0.0f;
|
|
int bestSizeError = 0x7FFFFFFF;
|
|
float bestRefreshRateError = 1e6f;
|
|
for (int i = 0; i < modes_length; i++) {
|
|
if (mode_info[i].mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) {
|
|
continue;
|
|
}
|
|
|
|
xcb_randr_mode_t *primary_output_info_modes = xcb_randr_get_output_info_modes(primary_output_info_reply);
|
|
int primary_output_info_modes_length = xcb_randr_get_output_info_modes_length(primary_output_info_reply);
|
|
|
|
bool validOutputMode = false;
|
|
for (int j = 0; j < primary_output_info_modes_length; j++) {
|
|
if (mode_info[i].id == primary_output_info_modes[j]) {
|
|
validOutputMode = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!validOutputMode) {
|
|
continue;
|
|
}
|
|
|
|
const int modeWidth = mode_info[i].width;
|
|
const int modeHeight = mode_info[i].height;
|
|
const float modeRefreshRate = mode_info[i].dot_clock / ((float)mode_info[i].htotal * (float)mode_info[i].vtotal);
|
|
|
|
const int dw = modeWidth - *desiredWidth;
|
|
const int dh = modeHeight - *desiredHeight;
|
|
const int sizeError = dw * dw + dh * dh;
|
|
const float refreshRateError = fabs(modeRefreshRate - *desiredRefreshRate);
|
|
if (sizeError < bestSizeError || (sizeError == bestSizeError && refreshRateError < bestRefreshRateError)) {
|
|
bestSizeError = sizeError;
|
|
bestRefreshRateError = refreshRateError;
|
|
bestMode = mode_info[i].id;
|
|
bestModeWidth = modeWidth;
|
|
bestModeHeight = modeHeight;
|
|
bestModeRefreshRate = modeRefreshRate;
|
|
}
|
|
}
|
|
|
|
xcb_randr_output_t *primary_crtc_info_outputs = xcb_randr_get_crtc_info_outputs(primary_crtc_info_reply);
|
|
int primary_crtc_info_outputs_length = xcb_randr_get_crtc_info_outputs_length(primary_crtc_info_reply);
|
|
|
|
xcb_randr_set_crtc_config(connection, primary_output_info_reply->crtc, XCB_TIME_CURRENT_TIME, XCB_TIME_CURRENT_TIME,
|
|
primary_crtc_info_reply->x, primary_crtc_info_reply->y, bestMode,
|
|
primary_crtc_info_reply->rotation, primary_crtc_info_outputs_length, primary_crtc_info_outputs);
|
|
|
|
*desiredWidth = bestModeWidth;
|
|
*desiredHeight = bestModeHeight;
|
|
*desiredRefreshRate = bestModeRefreshRate;
|
|
}
|
|
|
|
free(primary_output_info_reply);
|
|
free(primary_crtc_info_reply);
|
|
free(screen_resources_reply);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
|
|
#if defined(OS_LINUX_XCB_GLX)
|
|
glXDestroyWindow(window->xDisplay, window->glxWindow);
|
|
XFlush(window->xDisplay);
|
|
XCloseDisplay(window->xDisplay);
|
|
window->xDisplay = NULL;
|
|
#else
|
|
xcb_glx_delete_window(window->connection, window->glxWindow);
|
|
#endif
|
|
|
|
if (window->windowFullscreen) {
|
|
ChangeVideoMode_XcbRandR_1_4(window->connection, window->screen, NULL, NULL, NULL, &window->desktopWidth,
|
|
&window->desktopHeight, &window->desktopRefreshRate);
|
|
}
|
|
|
|
xcb_destroy_window(window->connection, window->window);
|
|
xcb_free_colormap(window->connection, window->colormap);
|
|
xcb_flush(window->connection);
|
|
xcb_disconnect(window->connection);
|
|
xcb_key_symbols_free(window->key_symbols);
|
|
}
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, const int width, const int height, const bool fullscreen) {
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = fullscreen;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
|
|
const char *displayName = NULL;
|
|
int screen_number = 0;
|
|
window->connection = xcb_connect(displayName, &screen_number);
|
|
if (xcb_connection_has_error(window->connection)) {
|
|
ksGpuWindow_Destroy(window);
|
|
Error("Failed to open XCB connection.");
|
|
return false;
|
|
}
|
|
|
|
const xcb_setup_t *setup = xcb_get_setup(window->connection);
|
|
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
|
|
for (int i = 0; i < screen_number; i++) {
|
|
xcb_screen_next(&iter);
|
|
}
|
|
window->screen = iter.data;
|
|
|
|
if (window->windowFullscreen) {
|
|
ChangeVideoMode_XcbRandR_1_4(window->connection, window->screen, &window->desktopWidth, &window->desktopHeight,
|
|
&window->desktopRefreshRate, &window->windowWidth, &window->windowHeight,
|
|
&window->windowRefreshRate);
|
|
} else {
|
|
ChangeVideoMode_XcbRandR_1_4(window->connection, window->screen, &window->desktopWidth, &window->desktopHeight,
|
|
&window->desktopRefreshRate, NULL, NULL, NULL);
|
|
window->windowRefreshRate = window->desktopRefreshRate;
|
|
}
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
#if defined(OS_LINUX_XCB_GLX)
|
|
window->xDisplay = XOpenDisplay(displayName);
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
window->xDisplay, screen_number);
|
|
#else
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
window->connection, screen_number);
|
|
#endif
|
|
|
|
// Create the color map.
|
|
window->colormap = xcb_generate_id(window->connection);
|
|
xcb_create_colormap(window->connection, XCB_COLORMAP_ALLOC_NONE, window->colormap, window->screen->root,
|
|
window->context.visualid);
|
|
|
|
// Create the window.
|
|
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
|
|
uint32_t value_list[5];
|
|
value_list[0] = window->screen->black_pixel;
|
|
value_list[1] = 0;
|
|
value_list[2] = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_BUTTON_PRESS;
|
|
value_list[3] = window->colormap;
|
|
value_list[4] = 0;
|
|
|
|
window->window = xcb_generate_id(window->connection);
|
|
xcb_create_window(window->connection, // xcb_connection_t * connection
|
|
XCB_COPY_FROM_PARENT, // uint8_t depth
|
|
window->window, // xcb_window_t wid
|
|
window->screen->root, // xcb_window_t parent
|
|
0, // int16_t x
|
|
0, // int16_t y
|
|
window->windowWidth, // uint16_t width
|
|
window->windowHeight, // uint16_t height
|
|
0, // uint16_t border_width
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, // uint16_t _class
|
|
window->context.visualid, // xcb_visualid_t visual
|
|
value_mask, // uint32_t value_mask
|
|
value_list); // const uint32_t * value_list
|
|
|
|
// Change the window title.
|
|
xcb_change_property(window->connection, XCB_PROP_MODE_REPLACE, window->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
|
strlen(WINDOW_TITLE), WINDOW_TITLE);
|
|
|
|
// Setup code that will send a notification when the window is destroyed.
|
|
xcb_intern_atom_cookie_t wm_protocols_cookie = xcb_intern_atom(window->connection, 1, 12, "WM_PROTOCOLS");
|
|
xcb_intern_atom_cookie_t wm_delete_window_cookie = xcb_intern_atom(window->connection, 0, 16, "WM_DELETE_WINDOW");
|
|
xcb_intern_atom_reply_t *wm_protocols_reply = xcb_intern_atom_reply(window->connection, wm_protocols_cookie, 0);
|
|
xcb_intern_atom_reply_t *wm_delete_window_reply = xcb_intern_atom_reply(window->connection, wm_delete_window_cookie, 0);
|
|
|
|
window->wm_delete_window_atom = wm_delete_window_reply->atom;
|
|
xcb_change_property(window->connection, XCB_PROP_MODE_REPLACE, window->window, wm_protocols_reply->atom, XCB_ATOM_ATOM, 32, 1,
|
|
&wm_delete_window_reply->atom);
|
|
|
|
free(wm_protocols_reply);
|
|
free(wm_delete_window_reply);
|
|
|
|
if (window->windowFullscreen) {
|
|
// Change the window to fullscreen
|
|
xcb_intern_atom_cookie_t wm_state_cookie = xcb_intern_atom(window->connection, 0, 13, "_NET_WM_STATE");
|
|
xcb_intern_atom_cookie_t wm_state_fullscreen_cookie =
|
|
xcb_intern_atom(window->connection, 0, 24, "_NET_WM_STATE_FULLSCREEN");
|
|
xcb_intern_atom_reply_t *wm_state_reply = xcb_intern_atom_reply(window->connection, wm_state_cookie, 0);
|
|
xcb_intern_atom_reply_t *wm_state_fullscreen_reply =
|
|
xcb_intern_atom_reply(window->connection, wm_state_fullscreen_cookie, 0);
|
|
|
|
xcb_client_message_event_t ev;
|
|
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
ev.format = 32;
|
|
ev.sequence = 0;
|
|
ev.window = window->window;
|
|
ev.type = wm_state_reply->atom;
|
|
ev.data.data32[0] = _NET_WM_STATE_ADD;
|
|
ev.data.data32[1] = wm_state_fullscreen_reply->atom;
|
|
ev.data.data32[2] = XCB_ATOM_NONE;
|
|
ev.data.data32[3] = 0;
|
|
ev.data.data32[4] = 0;
|
|
|
|
xcb_send_event(window->connection, 1, window->window,
|
|
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, (const char *)(&ev));
|
|
|
|
free(wm_state_reply);
|
|
free(wm_state_fullscreen_reply);
|
|
|
|
xcb_map_window(window->connection, window->window);
|
|
xcb_flush(window->connection);
|
|
} else {
|
|
// Make the window fixed size.
|
|
xcb_size_hints_t hints;
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.flags = XCB_SIZE_HINT_US_SIZE | XCB_SIZE_HINT_P_SIZE | XCB_SIZE_HINT_P_MIN_SIZE | XCB_SIZE_HINT_P_MAX_SIZE;
|
|
hints.min_width = window->windowWidth;
|
|
hints.max_width = window->windowWidth;
|
|
hints.min_height = window->windowHeight;
|
|
hints.max_height = window->windowHeight;
|
|
|
|
xcb_change_property(window->connection, XCB_PROP_MODE_REPLACE, window->window, XCB_ATOM_WM_NORMAL_HINTS,
|
|
XCB_ATOM_WM_SIZE_HINTS, 32, sizeof(hints) / 4, &hints);
|
|
|
|
// First map the window and then center the window on the screen.
|
|
xcb_map_window(window->connection, window->window);
|
|
const uint32_t coords[] = {(window->desktopWidth - window->windowWidth) / 2,
|
|
(window->desktopHeight - window->windowHeight) / 2};
|
|
xcb_configure_window(window->connection, window->window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords);
|
|
xcb_flush(window->connection);
|
|
}
|
|
|
|
window->key_symbols = xcb_key_symbols_alloc(window->connection);
|
|
|
|
#if defined(OS_LINUX_XCB_GLX)
|
|
window->glxWindow = glXCreateWindow(window->xDisplay, window->context.glxFBConfig, window->window, NULL);
|
|
#else
|
|
window->glxWindow = xcb_generate_id(window->connection);
|
|
xcb_glx_create_window(window->connection, screen_number, window->context.fbconfigid, window->window, window->glxWindow, 0,
|
|
NULL);
|
|
#endif
|
|
|
|
window->context.glxDrawable = window->glxWindow;
|
|
|
|
ksGpuContext_SetCurrent(&window->context);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ksGpuWindow_SupportedResolution(const int width, const int height) {
|
|
UNUSED_PARM(width);
|
|
UNUSED_PARM(height);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Exit(ksGpuWindow *window) { window->windowExit = true; }
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
xcb_generic_event_t *event = xcb_poll_for_event(window->connection);
|
|
if (event != NULL) {
|
|
const uint8_t event_code = (event->response_type & 0x7f);
|
|
switch (event_code) {
|
|
case XCB_CLIENT_MESSAGE: {
|
|
const xcb_client_message_event_t *client_message_event = (const xcb_client_message_event_t *)event;
|
|
if (client_message_event->data.data32[0] == window->wm_delete_window_atom) {
|
|
free(event);
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
break;
|
|
}
|
|
case XCB_KEY_PRESS: {
|
|
xcb_key_press_event_t *key_press_event = (xcb_key_press_event_t *)event;
|
|
const xcb_keysym_t keysym = xcb_key_press_lookup_keysym(window->key_symbols, key_press_event, 0);
|
|
if (keysym < 256 || keysym == XK_Escape) {
|
|
window->input.keyInput[keysym & 255] = true;
|
|
}
|
|
break;
|
|
}
|
|
case XCB_BUTTON_PRESS: {
|
|
const xcb_button_press_event_t *button_press_event = (const xcb_button_press_event_t *)event;
|
|
const int masks[5] = {XCB_BUTTON_MASK_1, XCB_BUTTON_MASK_2, XCB_BUTTON_MASK_3, XCB_BUTTON_MASK_4,
|
|
XCB_BUTTON_MASK_5};
|
|
for (int i = 0; i < 5; i++) {
|
|
if ((button_press_event->state & masks[i]) != 0) {
|
|
window->input.mouseInput[i] = true;
|
|
window->input.mouseInputX[i] = button_press_event->event_x;
|
|
window->input.mouseInputY[i] = button_press_event->event_y;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
free(event);
|
|
}
|
|
|
|
if (window->windowExit) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
|
|
if (window->windowActive == false) {
|
|
window->windowActive = true;
|
|
return KS_GPU_WINDOW_EVENT_ACTIVATED;
|
|
}
|
|
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
#elif defined(OS_LINUX_WAYLAND)
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
#endif
|
|
static void _keyboard_keymap_cb(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { close(fd); }
|
|
static void _keyboard_modifiers_cb(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed,
|
|
uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {}
|
|
|
|
static void _keyboard_enter_cb(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface,
|
|
struct wl_array *keys) {}
|
|
|
|
static void _keyboard_leave_cb(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {}
|
|
|
|
static void _pointer_leave_cb(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {}
|
|
|
|
static void _pointer_enter_cb(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx,
|
|
wl_fixed_t sy) {
|
|
wl_pointer_set_cursor(pointer, serial, NULL, 0, 0);
|
|
}
|
|
|
|
static void _pointer_motion_cb(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
window->input.mouseInputX[0] = wl_fixed_to_int(x);
|
|
window->input.mouseInputY[0] = wl_fixed_to_int(y);
|
|
}
|
|
|
|
static void _pointer_button_cb(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button,
|
|
uint32_t state) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
|
|
uint32_t button_id = 0;
|
|
switch (button) {
|
|
case BTN_LEFT:
|
|
button_id = 0;
|
|
break;
|
|
case BTN_MIDDLE:
|
|
button_id = 1;
|
|
break;
|
|
case BTN_RIGHT:
|
|
button_id = 2;
|
|
break;
|
|
}
|
|
|
|
window->input.mouseInput[button_id] = state;
|
|
}
|
|
|
|
static void _pointer_axis_cb(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {}
|
|
|
|
static void _keyboard_key_cb(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key,
|
|
uint32_t state) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
if (key == KEY_ESC) window->windowExit = true;
|
|
|
|
if (state) window->input.keyInput[key] = state;
|
|
}
|
|
|
|
const struct wl_pointer_listener pointer_listener = {
|
|
_pointer_enter_cb, _pointer_leave_cb, _pointer_motion_cb, _pointer_button_cb, _pointer_axis_cb,
|
|
};
|
|
|
|
const struct wl_keyboard_listener keyboard_listener = {
|
|
_keyboard_keymap_cb, _keyboard_enter_cb, _keyboard_leave_cb, _keyboard_key_cb, _keyboard_modifiers_cb,
|
|
};
|
|
|
|
static void _seat_capabilities_cb(void *data, struct wl_seat *seat, uint32_t caps) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !window->pointer) {
|
|
window->pointer = wl_seat_get_pointer(seat);
|
|
wl_pointer_add_listener(window->pointer, &pointer_listener, window);
|
|
} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && window->pointer) {
|
|
wl_pointer_destroy(window->pointer);
|
|
window->pointer = NULL;
|
|
}
|
|
|
|
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !window->keyboard) {
|
|
window->keyboard = wl_seat_get_keyboard(seat);
|
|
wl_keyboard_add_listener(window->keyboard, &keyboard_listener, window);
|
|
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && window->keyboard) {
|
|
wl_keyboard_destroy(window->keyboard);
|
|
window->keyboard = NULL;
|
|
}
|
|
}
|
|
|
|
const struct wl_seat_listener seat_listener = {
|
|
_seat_capabilities_cb,
|
|
};
|
|
|
|
static void _xdg_surface_configure_cb(void *data, struct zxdg_surface_v6 *surface, uint32_t serial) {
|
|
zxdg_surface_v6_ack_configure(surface, serial);
|
|
}
|
|
|
|
const struct zxdg_surface_v6_listener xdg_surface_listener = {
|
|
_xdg_surface_configure_cb,
|
|
};
|
|
|
|
static void _xdg_shell_ping_cb(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) { zxdg_shell_v6_pong(shell, serial); }
|
|
|
|
const struct zxdg_shell_v6_listener xdg_shell_listener = {
|
|
_xdg_shell_ping_cb,
|
|
};
|
|
|
|
static void _xdg_toplevel_configure_cb(void *data, struct zxdg_toplevel_v6 *toplevel, int32_t width, int32_t height,
|
|
struct wl_array *states) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
|
|
window->windowActive = false;
|
|
|
|
enum zxdg_toplevel_v6_state *state;
|
|
wl_array_for_each(state, states) {
|
|
switch (*state) {
|
|
case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
|
|
break;
|
|
case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
|
|
window->windowWidth = width;
|
|
window->windowWidth = height;
|
|
break;
|
|
case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
|
|
break;
|
|
case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
|
|
window->windowActive = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _xdg_toplevel_close_cb(void *data, struct zxdg_toplevel_v6 *toplevel) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
window->windowExit = true;
|
|
}
|
|
|
|
const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = {
|
|
_xdg_toplevel_configure_cb,
|
|
_xdg_toplevel_close_cb,
|
|
};
|
|
|
|
static void _registry_cb(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) {
|
|
ksGpuWindow *window = (ksGpuWindow *)data;
|
|
|
|
if (strcmp(interface, "wl_compositor") == 0) {
|
|
window->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
|
|
} else if (strcmp(interface, "zxdg_shell_v6") == 0) {
|
|
window->shell = wl_registry_bind(registry, id, &zxdg_shell_v6_interface, 1);
|
|
zxdg_shell_v6_add_listener(window->shell, &xdg_shell_listener, NULL);
|
|
} else if (strcmp(interface, "wl_seat") == 0) {
|
|
window->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1);
|
|
wl_seat_add_listener(window->seat, &seat_listener, window);
|
|
}
|
|
}
|
|
|
|
static void _registry_remove_cb(void *data, struct wl_registry *registry, uint32_t id) {}
|
|
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
const struct wl_registry_listener registry_listener = {_registry_cb, _registry_remove_cb};
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, const int width, const int height, const bool fullscreen) {
|
|
(void)queueIndex;
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->display = NULL;
|
|
window->surface = NULL;
|
|
window->registry = NULL;
|
|
window->compositor = NULL;
|
|
window->shell = NULL;
|
|
window->shell_surface = NULL;
|
|
|
|
window->keyboard = NULL;
|
|
window->pointer = NULL;
|
|
window->seat = NULL;
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = fullscreen;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
|
|
window->display = wl_display_connect(NULL);
|
|
if (window->display == NULL) {
|
|
Error("Can't connect to wayland display.");
|
|
return false;
|
|
}
|
|
|
|
window->registry = wl_display_get_registry(window->display);
|
|
wl_registry_add_listener(window->registry, ®istry_listener, window);
|
|
|
|
wl_display_roundtrip(window->display);
|
|
|
|
if (window->compositor == NULL) {
|
|
Error("Compositor protocol failed to bind");
|
|
return false;
|
|
}
|
|
|
|
if (window->shell == NULL) {
|
|
Error("Compositor is missing support for zxdg_shell_v6.");
|
|
return false;
|
|
}
|
|
|
|
window->surface = wl_compositor_create_surface(window->compositor);
|
|
if (window->surface == NULL) {
|
|
Error("Could not create compositor surface.");
|
|
return false;
|
|
}
|
|
|
|
window->shell_surface = zxdg_shell_v6_get_xdg_surface(window->shell, window->surface);
|
|
if (window->shell_surface == NULL) {
|
|
Error("Could not get shell surface.");
|
|
return false;
|
|
}
|
|
|
|
zxdg_surface_v6_add_listener(window->shell_surface, &xdg_surface_listener, window);
|
|
|
|
struct zxdg_toplevel_v6 *toplevel = zxdg_surface_v6_get_toplevel(window->shell_surface);
|
|
if (toplevel == NULL) {
|
|
Error("Could not get surface toplevel.");
|
|
return false;
|
|
}
|
|
|
|
zxdg_toplevel_v6_add_listener(toplevel, &xdg_toplevel_listener, window);
|
|
|
|
zxdg_toplevel_v6_set_title(toplevel, WINDOW_TITLE);
|
|
zxdg_toplevel_v6_set_app_id(toplevel, APPLICATION_NAME);
|
|
zxdg_toplevel_v6_set_min_size(toplevel, width, height);
|
|
zxdg_toplevel_v6_set_max_size(toplevel, width, height);
|
|
|
|
wl_surface_commit(window->surface);
|
|
|
|
window->context.native_window = wl_egl_window_create(window->surface, width, height);
|
|
|
|
if (window->context.native_window == EGL_NO_SURFACE) {
|
|
ksGpuWindow_Destroy(window);
|
|
Error("Could not create wayland egl window.");
|
|
return false;
|
|
}
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, window->display);
|
|
|
|
ksGpuContext_SetCurrent(&window->context);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
if (window->pointer != NULL) wl_pointer_destroy(window->pointer);
|
|
if (window->keyboard != NULL) wl_keyboard_destroy(window->keyboard);
|
|
if (window->seat != NULL) wl_seat_destroy(window->seat);
|
|
|
|
wl_egl_window_destroy(window->context.native_window);
|
|
|
|
if (window->compositor != NULL) wl_compositor_destroy(window->compositor);
|
|
if (window->registry != NULL) wl_registry_destroy(window->registry);
|
|
if (window->shell_surface != NULL) zxdg_surface_v6_destroy(window->shell_surface);
|
|
if (window->shell != NULL) zxdg_shell_v6_destroy(window->shell);
|
|
if (window->surface != NULL) wl_surface_destroy(window->surface);
|
|
if (window->display != NULL) wl_display_disconnect(window->display);
|
|
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
}
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
while (wl_display_prepare_read(window->display) != 0) wl_display_dispatch_pending(window->display);
|
|
|
|
if (wl_display_flush(window->display) < 0 && errno != EAGAIN) {
|
|
wl_display_cancel_read(window->display);
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
struct pollfd fds[] = {
|
|
{wl_display_get_fd(window->display), POLLIN},
|
|
};
|
|
if (poll(fds, 1, 0) > 0) {
|
|
wl_display_read_events(window->display);
|
|
wl_display_dispatch_pending(window->display);
|
|
} else {
|
|
wl_display_cancel_read(window->display);
|
|
}
|
|
|
|
if (window->windowExit) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
/*
|
|
* TODO:
|
|
* This is a work around for ksKeyboardKey naming collision
|
|
* with the definitions from <linux/input.h>.
|
|
* The proper fix for this is to rename the key enums.
|
|
*/
|
|
|
|
#undef KEY_A
|
|
#undef KEY_B
|
|
#undef KEY_C
|
|
#undef KEY_D
|
|
#undef KEY_E
|
|
#undef KEY_F
|
|
#undef KEY_G
|
|
#undef KEY_H
|
|
#undef KEY_I
|
|
#undef KEY_J
|
|
#undef KEY_K
|
|
#undef KEY_L
|
|
#undef KEY_M
|
|
#undef KEY_N
|
|
#undef KEY_O
|
|
#undef KEY_P
|
|
#undef KEY_Q
|
|
#undef KEY_R
|
|
#undef KEY_S
|
|
#undef KEY_T
|
|
#undef KEY_U
|
|
#undef KEY_V
|
|
#undef KEY_W
|
|
#undef KEY_X
|
|
#undef KEY_Y
|
|
#undef KEY_Z
|
|
#undef KEY_TAB
|
|
|
|
typedef enum // from <linux/input.h>
|
|
{ KEY_A = 30,
|
|
KEY_B = 48,
|
|
KEY_C = 46,
|
|
KEY_D = 32,
|
|
KEY_E = 18,
|
|
KEY_F = 33,
|
|
KEY_G = 34,
|
|
KEY_H = 35,
|
|
KEY_I = 23,
|
|
KEY_J = 36,
|
|
KEY_K = 37,
|
|
KEY_L = 38,
|
|
KEY_M = 50,
|
|
KEY_N = 49,
|
|
KEY_O = 24,
|
|
KEY_P = 25,
|
|
KEY_Q = 16,
|
|
KEY_R = 19,
|
|
KEY_S = 31,
|
|
KEY_T = 20,
|
|
KEY_U = 22,
|
|
KEY_V = 47,
|
|
KEY_W = 17,
|
|
KEY_X = 45,
|
|
KEY_Y = 21,
|
|
KEY_Z = 44,
|
|
KEY_TAB = 15,
|
|
KEY_RETURN = KEY_ENTER,
|
|
KEY_ESCAPE = KEY_ESC,
|
|
KEY_SHIFT_LEFT = KEY_LEFTSHIFT,
|
|
KEY_CTRL_LEFT = KEY_LEFTCTRL,
|
|
KEY_ALT_LEFT = KEY_LEFTALT,
|
|
KEY_CURSOR_UP = KEY_UP,
|
|
KEY_CURSOR_DOWN = KEY_DOWN,
|
|
KEY_CURSOR_LEFT = KEY_LEFT,
|
|
KEY_CURSOR_RIGHT = KEY_RIGHT } ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = BTN_LEFT, MOUSE_MIDDLE = BTN_MIDDLE, MOUSE_RIGHT = BTN_RIGHT } ksMouseButton;
|
|
|
|
#elif defined(OS_APPLE_MACOS)
|
|
|
|
typedef enum {
|
|
KEY_A = 0x00,
|
|
KEY_B = 0x0B,
|
|
KEY_C = 0x08,
|
|
KEY_D = 0x02,
|
|
KEY_E = 0x0E,
|
|
KEY_F = 0x03,
|
|
KEY_G = 0x05,
|
|
KEY_H = 0x04,
|
|
KEY_I = 0x22,
|
|
KEY_J = 0x26,
|
|
KEY_K = 0x28,
|
|
KEY_L = 0x25,
|
|
KEY_M = 0x2E,
|
|
KEY_N = 0x2D,
|
|
KEY_O = 0x1F,
|
|
KEY_P = 0x23,
|
|
KEY_Q = 0x0C,
|
|
KEY_R = 0x0F,
|
|
KEY_S = 0x01,
|
|
KEY_T = 0x11,
|
|
KEY_U = 0x20,
|
|
KEY_V = 0x09,
|
|
KEY_W = 0x0D,
|
|
KEY_X = 0x07,
|
|
KEY_Y = 0x10,
|
|
KEY_Z = 0x06,
|
|
KEY_RETURN = 0x24,
|
|
KEY_TAB = 0x30,
|
|
KEY_ESCAPE = 0x35,
|
|
KEY_SHIFT_LEFT = 0x38,
|
|
KEY_CTRL_LEFT = 0x3B,
|
|
KEY_ALT_LEFT = 0x3A,
|
|
KEY_CURSOR_UP = 0x7E,
|
|
KEY_CURSOR_DOWN = 0x7D,
|
|
KEY_CURSOR_LEFT = 0x7B,
|
|
KEY_CURSOR_RIGHT = 0x7C
|
|
} ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = 0, MOUSE_RIGHT = 1 } ksMouseButton;
|
|
|
|
NSAutoreleasePool *autoReleasePool;
|
|
|
|
@interface MyNSWindow : NSWindow
|
|
- (BOOL)canBecomeMainWindow;
|
|
- (BOOL)canBecomeKeyWindow;
|
|
- (BOOL)acceptsFirstResponder;
|
|
- (void)keyDown:(NSEvent *)event;
|
|
@end
|
|
|
|
@implementation MyNSWindow
|
|
- (BOOL)canBecomeMainWindow {
|
|
return YES;
|
|
}
|
|
- (BOOL)canBecomeKeyWindow {
|
|
return YES;
|
|
}
|
|
- (BOOL)acceptsFirstResponder {
|
|
return YES;
|
|
}
|
|
- (void)keyDown:(NSEvent *)event {
|
|
}
|
|
@end
|
|
|
|
@interface MyNSView : NSView
|
|
- (BOOL)acceptsFirstResponder;
|
|
- (void)keyDown:(NSEvent *)event;
|
|
@end
|
|
|
|
@implementation MyNSView
|
|
- (BOOL)acceptsFirstResponder {
|
|
return YES;
|
|
}
|
|
- (void)keyDown:(NSEvent *)event {
|
|
}
|
|
@end
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
|
|
if (window->windowFullscreen) {
|
|
CGDisplaySetDisplayMode(window->display, window->desktopDisplayMode, NULL);
|
|
CGDisplayModeRelease(window->desktopDisplayMode);
|
|
window->desktopDisplayMode = NULL;
|
|
}
|
|
if (window->nsWindow) {
|
|
[window->nsWindow release];
|
|
window->nsWindow = nil;
|
|
}
|
|
if (window->nsView) {
|
|
[window->nsView release];
|
|
window->nsView = nil;
|
|
}
|
|
}
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, const int width, const int height, const bool fullscreen) {
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = fullscreen;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
|
|
// Get a list of all available displays.
|
|
CGDirectDisplayID displays[32];
|
|
CGDisplayCount displayCount = 0;
|
|
CGDisplayErr err = CGGetActiveDisplayList(32, displays, &displayCount);
|
|
if (err != CGDisplayNoErr) {
|
|
return false;
|
|
}
|
|
// Use the main display.
|
|
window->display = displays[0];
|
|
window->desktopDisplayMode = CGDisplayCopyDisplayMode(window->display);
|
|
|
|
// If fullscreen then switch to the best matching display mode.
|
|
if (window->windowFullscreen) {
|
|
CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(window->display, NULL);
|
|
CFIndex displayModeCount = CFArrayGetCount(displayModes);
|
|
CGDisplayModeRef bestDisplayMode = nil;
|
|
size_t bestDisplayWidth = 0;
|
|
size_t bestDisplayHeight = 0;
|
|
float bestDisplayRefreshRate = 0;
|
|
size_t bestError = 0x7FFFFFFF;
|
|
for (CFIndex i = 0; i < displayModeCount; i++) {
|
|
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
|
|
|
|
const size_t modeWidth = CGDisplayModeGetWidth(mode);
|
|
const size_t modeHeight = CGDisplayModeGetHeight(mode);
|
|
const double modeRefreshRate = CGDisplayModeGetRefreshRate(mode);
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
CFStringRef modePixelEncoding = CGDisplayModeCopyPixelEncoding(mode);
|
|
#pragma GCC diagnostic pop
|
|
const bool modeBitsPerPixelIs32 =
|
|
(CFStringCompare(modePixelEncoding, CFSTR(IO32BitDirectPixels), 0) == kCFCompareEqualTo);
|
|
CFRelease(modePixelEncoding);
|
|
|
|
if (modeBitsPerPixelIs32) {
|
|
const size_t dw = modeWidth - width;
|
|
const size_t dh = modeHeight - height;
|
|
const size_t error = dw * dw + dh * dh;
|
|
if (error < bestError) {
|
|
bestError = error;
|
|
bestDisplayMode = mode;
|
|
bestDisplayWidth = modeWidth;
|
|
bestDisplayHeight = modeHeight;
|
|
bestDisplayRefreshRate = (float)modeRefreshRate;
|
|
}
|
|
}
|
|
}
|
|
CGDisplayErr err = CGDisplaySetDisplayMode(window->display, bestDisplayMode, NULL);
|
|
if (err != CGDisplayNoErr) {
|
|
CFRelease(displayModes);
|
|
return false;
|
|
}
|
|
CFRelease(displayModes);
|
|
window->windowWidth = (int)bestDisplayWidth;
|
|
window->windowHeight = (int)bestDisplayHeight;
|
|
window->windowRefreshRate = (bestDisplayRefreshRate > 0.0f) ? bestDisplayRefreshRate : 60.0f;
|
|
} else {
|
|
const float desktopDisplayRefreshRate = (float)CGDisplayModeGetRefreshRate(window->desktopDisplayMode);
|
|
window->windowRefreshRate = (desktopDisplayRefreshRate > 0.0f) ? desktopDisplayRefreshRate : 60.0f;
|
|
}
|
|
|
|
if (window->windowFullscreen) {
|
|
NSScreen *screen = [NSScreen mainScreen];
|
|
NSRect screenRect = [screen frame];
|
|
|
|
window->nsView = [MyNSView alloc];
|
|
[window->nsView initWithFrame:screenRect];
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
const int style = NSBorderlessWindowMask;
|
|
#pragma GCC diagnostic pop
|
|
|
|
window->nsWindow = [MyNSWindow alloc];
|
|
[window->nsWindow initWithContentRect:screenRect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
|
|
[window->nsWindow setOpaque:YES];
|
|
[window->nsWindow setLevel:NSMainMenuWindowLevel + 1];
|
|
[window->nsWindow setContentView:window->nsView];
|
|
[window->nsWindow makeMainWindow];
|
|
[window->nsWindow makeKeyAndOrderFront:nil];
|
|
[window->nsWindow makeFirstResponder:nil];
|
|
} else {
|
|
NSScreen *screen = [NSScreen mainScreen];
|
|
NSRect screenRect = [screen frame];
|
|
|
|
NSRect windowRect;
|
|
windowRect.origin.x = (screenRect.size.width - width) / 2;
|
|
windowRect.origin.y = (screenRect.size.height - height) / 2;
|
|
windowRect.size.width = width;
|
|
windowRect.size.height = height;
|
|
|
|
window->nsView = [MyNSView alloc];
|
|
[window->nsView initWithFrame:windowRect];
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
// Fixed size window.
|
|
const int style = NSTitledWindowMask; // | NSClosableWindowMask | NSResizableWindowMask;
|
|
#pragma GCC diagnostic pop
|
|
|
|
window->nsWindow = [MyNSWindow alloc];
|
|
[window->nsWindow initWithContentRect:windowRect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen];
|
|
[window->nsWindow setTitle:@WINDOW_TITLE];
|
|
[window->nsWindow setOpaque:YES];
|
|
[window->nsWindow setContentView:window->nsView];
|
|
[window->nsWindow makeMainWindow];
|
|
[window->nsWindow makeKeyAndOrderFront:nil];
|
|
[window->nsWindow makeFirstResponder:nil];
|
|
}
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
window->display);
|
|
|
|
[window->context.nsContext setView:window->nsView];
|
|
|
|
ksGpuContext_SetCurrent(&window->context);
|
|
|
|
// The color buffers are not cleared by default.
|
|
for (int i = 0; i < 2; i++) {
|
|
GL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
|
|
GL(glClear(GL_COLOR_BUFFER_BIT));
|
|
CGLFlushDrawable(window->context.cglContext);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ksGpuWindow_SupportedResolution(const int width, const int height) {
|
|
UNUSED_PARM(width);
|
|
UNUSED_PARM(height);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Exit(ksGpuWindow *window) { window->windowExit = true; }
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
[autoReleasePool release];
|
|
autoReleasePool = [[NSAutoreleasePool alloc] init];
|
|
|
|
for (;;) {
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
NSEvent *event =
|
|
[NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
|
if (event == nil) {
|
|
break;
|
|
}
|
|
|
|
if (event.type == NSKeyDown) {
|
|
unsigned short key = [event keyCode];
|
|
if (key >= 0 && key < 256) {
|
|
window->input.keyInput[key] = true;
|
|
}
|
|
} else if (event.type == NSLeftMouseDown) {
|
|
NSPoint point = [event locationInWindow];
|
|
window->input.mouseInput[MOUSE_LEFT] = true;
|
|
window->input.mouseInputX[MOUSE_LEFT] = point.x;
|
|
window->input.mouseInputY[MOUSE_LEFT] = point.y - 1; // change to zero-based
|
|
} else if (event.type == NSRightMouseDown) {
|
|
NSPoint point = [event locationInWindow];
|
|
window->input.mouseInput[MOUSE_RIGHT] = true;
|
|
window->input.mouseInputX[MOUSE_RIGHT] = point.x;
|
|
window->input.mouseInputY[MOUSE_RIGHT] = point.y - 1; // change to zero-based
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
|
|
[NSApp sendEvent:event];
|
|
}
|
|
|
|
if (window->windowExit) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
|
|
if (window->windowActive == false) {
|
|
window->windowActive = true;
|
|
return KS_GPU_WINDOW_EVENT_ACTIVATED;
|
|
}
|
|
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
#elif defined(OS_APPLE_IOS)
|
|
|
|
typedef enum {
|
|
KEY_A = 0x00,
|
|
KEY_B = 0x0B,
|
|
KEY_C = 0x08,
|
|
KEY_D = 0x02,
|
|
KEY_E = 0x0E,
|
|
KEY_F = 0x03,
|
|
KEY_G = 0x05,
|
|
KEY_H = 0x04,
|
|
KEY_I = 0x22,
|
|
KEY_J = 0x26,
|
|
KEY_K = 0x28,
|
|
KEY_L = 0x25,
|
|
KEY_M = 0x2E,
|
|
KEY_N = 0x2D,
|
|
KEY_O = 0x1F,
|
|
KEY_P = 0x23,
|
|
KEY_Q = 0x0C,
|
|
KEY_R = 0x0F,
|
|
KEY_S = 0x01,
|
|
KEY_T = 0x11,
|
|
KEY_U = 0x20,
|
|
KEY_V = 0x09,
|
|
KEY_W = 0x0D,
|
|
KEY_X = 0x07,
|
|
KEY_Y = 0x10,
|
|
KEY_Z = 0x06,
|
|
KEY_RETURN = 0x24,
|
|
KEY_TAB = 0x30,
|
|
KEY_ESCAPE = 0x35,
|
|
KEY_SHIFT_LEFT = 0x38,
|
|
KEY_CTRL_LEFT = 0x3B,
|
|
KEY_ALT_LEFT = 0x3A,
|
|
KEY_CURSOR_UP = 0x7E,
|
|
KEY_CURSOR_DOWN = 0x7D,
|
|
KEY_CURSOR_LEFT = 0x7B,
|
|
KEY_CURSOR_RIGHT = 0x7C
|
|
} ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = 0, MOUSE_RIGHT = 1 } ksMouseButton;
|
|
|
|
static UIView *myUIView;
|
|
static UIWindow *myUIWindow;
|
|
|
|
@interface MyUIView : UIView
|
|
@end
|
|
|
|
@implementation MyUIView
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frameRect {
|
|
self = [super initWithFrame:frameRect];
|
|
if (self) {
|
|
self.contentScaleFactor = UIScreen.mainScreen.nativeScale;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (Class)layerClass {
|
|
return [CAEAGLLayer class];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface MyUIViewController : UIViewController
|
|
@end
|
|
|
|
@implementation MyUIViewController
|
|
|
|
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
|
return UIInterfaceOrientationMaskLandscape;
|
|
}
|
|
|
|
- (BOOL)shouldAutorotate {
|
|
return TRUE;
|
|
}
|
|
|
|
@end
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
window->uiWindow = nil;
|
|
window->uiView = nil;
|
|
}
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, const int width, const int height, const bool fullscreen) {
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = fullscreen;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
window->uiView = myUIView;
|
|
window->uiWindow = myUIWindow;
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
// ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
// window->display);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ksGpuWindow_SupportedResolution(const int width, const int height) {
|
|
UNUSED_PARM(width);
|
|
UNUSED_PARM(height);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Exit(ksGpuWindow *window) { window->windowExit = true; }
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
if (window->windowExit) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
|
|
if (window->windowActive == false) {
|
|
window->windowActive = true;
|
|
return KS_GPU_WINDOW_EVENT_ACTIVATED;
|
|
}
|
|
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
#elif defined(OS_ANDROID)
|
|
|
|
typedef enum // https://developer.android.com/ndk/reference/group___input.html
|
|
{ KEY_A = AKEYCODE_A,
|
|
KEY_B = AKEYCODE_B,
|
|
KEY_C = AKEYCODE_C,
|
|
KEY_D = AKEYCODE_D,
|
|
KEY_E = AKEYCODE_E,
|
|
KEY_F = AKEYCODE_F,
|
|
KEY_G = AKEYCODE_G,
|
|
KEY_H = AKEYCODE_H,
|
|
KEY_I = AKEYCODE_I,
|
|
KEY_J = AKEYCODE_J,
|
|
KEY_K = AKEYCODE_K,
|
|
KEY_L = AKEYCODE_L,
|
|
KEY_M = AKEYCODE_M,
|
|
KEY_N = AKEYCODE_N,
|
|
KEY_O = AKEYCODE_O,
|
|
KEY_P = AKEYCODE_P,
|
|
KEY_Q = AKEYCODE_Q,
|
|
KEY_R = AKEYCODE_R,
|
|
KEY_S = AKEYCODE_S,
|
|
KEY_T = AKEYCODE_T,
|
|
KEY_U = AKEYCODE_U,
|
|
KEY_V = AKEYCODE_V,
|
|
KEY_W = AKEYCODE_W,
|
|
KEY_X = AKEYCODE_X,
|
|
KEY_Y = AKEYCODE_Y,
|
|
KEY_Z = AKEYCODE_Z,
|
|
KEY_RETURN = AKEYCODE_ENTER,
|
|
KEY_TAB = AKEYCODE_TAB,
|
|
KEY_ESCAPE = AKEYCODE_ESCAPE,
|
|
KEY_SHIFT_LEFT = AKEYCODE_SHIFT_LEFT,
|
|
KEY_CTRL_LEFT = AKEYCODE_CTRL_LEFT,
|
|
KEY_ALT_LEFT = AKEYCODE_ALT_LEFT,
|
|
KEY_CURSOR_UP = AKEYCODE_DPAD_UP,
|
|
KEY_CURSOR_DOWN = AKEYCODE_DPAD_DOWN,
|
|
KEY_CURSOR_LEFT = AKEYCODE_DPAD_LEFT,
|
|
KEY_CURSOR_RIGHT = AKEYCODE_DPAD_RIGHT } ksKeyboardKey;
|
|
|
|
typedef enum { MOUSE_LEFT = 0, MOUSE_RIGHT = 1 } ksMouseButton;
|
|
|
|
static void app_handle_cmd(struct android_app *app, int32_t cmd) {
|
|
ksGpuWindow *window = (ksGpuWindow *)app->userData;
|
|
|
|
switch (cmd) {
|
|
// There is no APP_CMD_CREATE. The ANativeActivity creates the
|
|
// application thread from onCreate(). The application thread
|
|
// then calls android_main().
|
|
case APP_CMD_START: {
|
|
Print("onStart()");
|
|
Print(" APP_CMD_START");
|
|
break;
|
|
}
|
|
case APP_CMD_RESUME: {
|
|
Print("onResume()");
|
|
Print(" APP_CMD_RESUME");
|
|
window->resumed = true;
|
|
break;
|
|
}
|
|
case APP_CMD_PAUSE: {
|
|
Print("onPause()");
|
|
Print(" APP_CMD_PAUSE");
|
|
window->resumed = false;
|
|
break;
|
|
}
|
|
case APP_CMD_STOP: {
|
|
Print("onStop()");
|
|
Print(" APP_CMD_STOP");
|
|
break;
|
|
}
|
|
case APP_CMD_DESTROY: {
|
|
Print("onDestroy()");
|
|
Print(" APP_CMD_DESTROY");
|
|
window->nativeWindow = NULL;
|
|
break;
|
|
}
|
|
case APP_CMD_INIT_WINDOW: {
|
|
Print("surfaceCreated()");
|
|
Print(" APP_CMD_INIT_WINDOW");
|
|
window->nativeWindow = app->window;
|
|
break;
|
|
}
|
|
case APP_CMD_TERM_WINDOW: {
|
|
Print("surfaceDestroyed()");
|
|
Print(" APP_CMD_TERM_WINDOW");
|
|
window->nativeWindow = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32_t app_handle_input(struct android_app *app, AInputEvent *event) {
|
|
ksGpuWindow *window = (ksGpuWindow *)app->userData;
|
|
|
|
const int type = AInputEvent_getType(event);
|
|
if (type == AINPUT_EVENT_TYPE_KEY) {
|
|
int keyCode = AKeyEvent_getKeyCode(event);
|
|
const int action = AKeyEvent_getAction(event);
|
|
if (action == AKEY_EVENT_ACTION_DOWN) {
|
|
// Translate controller input to useful keys.
|
|
switch (keyCode) {
|
|
case AKEYCODE_BUTTON_A:
|
|
keyCode = AKEYCODE_Q;
|
|
break;
|
|
case AKEYCODE_BUTTON_B:
|
|
keyCode = AKEYCODE_W;
|
|
break;
|
|
case AKEYCODE_BUTTON_X:
|
|
keyCode = AKEYCODE_E;
|
|
break;
|
|
case AKEYCODE_BUTTON_Y:
|
|
keyCode = AKEYCODE_M;
|
|
break;
|
|
case AKEYCODE_BUTTON_START:
|
|
keyCode = AKEYCODE_L;
|
|
break;
|
|
case AKEYCODE_BUTTON_SELECT:
|
|
keyCode = AKEYCODE_ESCAPE;
|
|
break;
|
|
}
|
|
if (keyCode >= 0 && keyCode < 256) {
|
|
window->input.keyInput[keyCode] = true;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
} else if (type == AINPUT_EVENT_TYPE_MOTION) {
|
|
const int source = AInputEvent_getSource(event);
|
|
// Events with source == AINPUT_SOURCE_TOUCHSCREEN come from the phone's builtin touch screen.
|
|
// Events with source == AINPUT_SOURCE_MOUSE come from the trackpad on the right side of the GearVR.
|
|
if (source == AINPUT_SOURCE_TOUCHSCREEN || source == AINPUT_SOURCE_MOUSE) {
|
|
const int action = AKeyEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
|
|
const float x = AMotionEvent_getRawX(event, 0);
|
|
const float y = AMotionEvent_getRawY(event, 0);
|
|
if (action == AMOTION_EVENT_ACTION_UP) {
|
|
window->input.mouseInput[MOUSE_LEFT] = true;
|
|
window->input.mouseInputX[MOUSE_LEFT] = (int)x;
|
|
window->input.mouseInputY[MOUSE_LEFT] = (int)y;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ksGpuWindow_Destroy(ksGpuWindow *window) {
|
|
ksGpuContext_Destroy(&window->context);
|
|
ksGpuDevice_Destroy(&window->device);
|
|
|
|
if (window->display != 0) {
|
|
EGL(eglTerminate(window->display));
|
|
window->display = 0;
|
|
}
|
|
|
|
if (window->app != NULL) {
|
|
(*window->java.vm)->DetachCurrentThread(window->java.vm);
|
|
window->java.vm = NULL;
|
|
window->java.env = NULL;
|
|
window->java.activity = 0;
|
|
}
|
|
}
|
|
|
|
static float GetDisplayRefreshRate(const Java_t *java) {
|
|
// Retrieve Context.WINDOW_SERVICE.
|
|
jclass contextClass = (*java->env)->FindClass(java->env, "android/content/Context");
|
|
jfieldID field_WINDOW_SERVICE = (*java->env)->GetStaticFieldID(java->env, contextClass, "WINDOW_SERVICE", "Ljava/lang/String;");
|
|
jobject WINDOW_SERVICE = (*java->env)->GetStaticObjectField(java->env, contextClass, field_WINDOW_SERVICE);
|
|
(*java->env)->DeleteLocalRef(java->env, contextClass);
|
|
|
|
// WindowManager windowManager = (WindowManager) activity.getSystemService( Context.WINDOW_SERVICE );
|
|
const jclass activityClass = (*java->env)->GetObjectClass(java->env, java->activity);
|
|
const jmethodID getSystemServiceMethodId =
|
|
(*java->env)->GetMethodID(java->env, activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
|
|
const jobject windowManager =
|
|
(*java->env)->CallObjectMethod(java->env, java->activity, getSystemServiceMethodId, WINDOW_SERVICE);
|
|
(*java->env)->DeleteLocalRef(java->env, activityClass);
|
|
|
|
// Display display = windowManager.getDefaultDisplay();
|
|
const jclass windowManagerClass = (*java->env)->GetObjectClass(java->env, windowManager);
|
|
const jmethodID getDefaultDisplayMethodId =
|
|
(*java->env)->GetMethodID(java->env, windowManagerClass, "getDefaultDisplay", "()Landroid/view/Display;");
|
|
const jobject display = (*java->env)->CallObjectMethod(java->env, windowManager, getDefaultDisplayMethodId);
|
|
(*java->env)->DeleteLocalRef(java->env, windowManagerClass);
|
|
|
|
// float refreshRate = display.getRefreshRate();
|
|
const jclass displayClass = (*java->env)->GetObjectClass(java->env, display);
|
|
const jmethodID getRefreshRateMethodId = (*java->env)->GetMethodID(java->env, displayClass, "getRefreshRate", "()F");
|
|
const float refreshRate = (*java->env)->CallFloatMethod(java->env, display, getRefreshRateMethodId);
|
|
(*java->env)->DeleteLocalRef(java->env, displayClass);
|
|
|
|
(*java->env)->DeleteLocalRef(java->env, display);
|
|
(*java->env)->DeleteLocalRef(java->env, windowManager);
|
|
(*java->env)->DeleteLocalRef(java->env, WINDOW_SERVICE);
|
|
|
|
return refreshRate;
|
|
}
|
|
|
|
struct android_app *global_app;
|
|
|
|
bool ksGpuWindow_Create(ksGpuWindow *window, ksDriverInstance *instance, const ksGpuQueueInfo *queueInfo, const int queueIndex,
|
|
const ksGpuSurfaceColorFormat colorFormat, const ksGpuSurfaceDepthFormat depthFormat,
|
|
const ksGpuSampleCount sampleCount, const int width, const int height, const bool fullscreen) {
|
|
memset(window, 0, sizeof(ksGpuWindow));
|
|
|
|
window->colorFormat = colorFormat;
|
|
window->depthFormat = depthFormat;
|
|
window->sampleCount = sampleCount;
|
|
window->windowWidth = width;
|
|
window->windowHeight = height;
|
|
window->windowSwapInterval = 1;
|
|
window->windowRefreshRate = 60.0f;
|
|
window->windowFullscreen = true;
|
|
window->windowActive = false;
|
|
window->windowExit = false;
|
|
window->lastSwapTime = GetTimeNanoseconds();
|
|
|
|
window->app = global_app;
|
|
window->nativeWindow = NULL;
|
|
window->resumed = false;
|
|
|
|
if (window->app != NULL) {
|
|
window->app->userData = window;
|
|
window->app->onAppCmd = app_handle_cmd;
|
|
window->app->onInputEvent = app_handle_input;
|
|
window->java.vm = window->app->activity->vm;
|
|
(*window->java.vm)->AttachCurrentThread(window->java.vm, &window->java.env, NULL);
|
|
window->java.activity = window->app->activity->clazz;
|
|
|
|
window->windowRefreshRate = GetDisplayRefreshRate(&window->java);
|
|
|
|
// Keep the display on and bright.
|
|
// Also make sure there is only one "HWC" next to the "FB TARGET" (adb shell dumpsys SurfaceFlinger).
|
|
ANativeActivity_setWindowFlags(window->app->activity, AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_KEEP_SCREEN_ON, 0);
|
|
}
|
|
|
|
window->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
EGL(eglInitialize(window->display, &window->majorVersion, &window->minorVersion));
|
|
|
|
ksGpuDevice_Create(&window->device, instance, queueInfo);
|
|
ksGpuContext_CreateForSurface(&window->context, &window->device, queueIndex, colorFormat, depthFormat, sampleCount,
|
|
window->display);
|
|
ksGpuContext_SetCurrent(&window->context);
|
|
|
|
GlInitExtensions();
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ksGpuWindow_SupportedResolution(const int width, const int height) {
|
|
UNUSED_PARM(width);
|
|
UNUSED_PARM(height);
|
|
|
|
// Assume the HWC can handle any window size.
|
|
return true;
|
|
}
|
|
|
|
void ksGpuWindow_Exit(ksGpuWindow *window) {
|
|
// Call finish() on the activity and ksGpuWindow_ProcessEvents will handle the rest.
|
|
ANativeActivity_finish(window->app->activity);
|
|
}
|
|
|
|
ksGpuWindowEvent ksGpuWindow_ProcessEvents(ksGpuWindow *window) {
|
|
if (window->app == NULL) {
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
const bool windowWasActive = window->windowActive;
|
|
|
|
for (;;) {
|
|
int events;
|
|
struct android_poll_source *source;
|
|
const int timeoutMilliseconds = (window->windowActive == false && window->app->destroyRequested == 0) ? -1 : 0;
|
|
if (ALooper_pollAll(timeoutMilliseconds, NULL, &events, (void **)&source) < 0) {
|
|
break;
|
|
}
|
|
|
|
if (source != NULL) {
|
|
source->process(window->app, source);
|
|
}
|
|
|
|
if (window->nativeWindow != NULL && window->context.mainSurface == window->context.tinySurface) {
|
|
Print(" ANativeWindow_setBuffersGeometry %d x %d", window->windowWidth, window->windowHeight);
|
|
ANativeWindow_setBuffersGeometry(window->nativeWindow, window->windowWidth, window->windowHeight, 0);
|
|
|
|
const EGLint surfaceAttribs[] = {EGL_NONE};
|
|
Print(" mainSurface = eglCreateWindowSurface( nativeWindow )");
|
|
window->context.mainSurface =
|
|
eglCreateWindowSurface(window->context.display, window->context.config, window->nativeWindow, surfaceAttribs);
|
|
if (window->context.mainSurface == EGL_NO_SURFACE) {
|
|
Error(" eglCreateWindowSurface() failed: %s", EglErrorString(eglGetError()));
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
Print(" eglMakeCurrent( mainSurface )");
|
|
EGL(eglMakeCurrent(window->context.display, window->context.mainSurface, window->context.mainSurface,
|
|
window->context.context));
|
|
|
|
eglQuerySurface(window->context.display, window->context.mainSurface, EGL_WIDTH, &window->windowWidth);
|
|
eglQuerySurface(window->context.display, window->context.mainSurface, EGL_HEIGHT, &window->windowHeight);
|
|
}
|
|
|
|
if (window->resumed != false && window->nativeWindow != NULL) {
|
|
window->windowActive = true;
|
|
} else {
|
|
window->windowActive = false;
|
|
}
|
|
|
|
if (window->nativeWindow == NULL && window->context.mainSurface != window->context.tinySurface) {
|
|
Print(" eglMakeCurrent( tinySurface )");
|
|
EGL(eglMakeCurrent(window->context.display, window->context.tinySurface, window->context.tinySurface,
|
|
window->context.context));
|
|
Print(" eglDestroySurface( mainSurface )");
|
|
EGL(eglDestroySurface(window->context.display, window->context.mainSurface));
|
|
window->context.mainSurface = window->context.tinySurface;
|
|
}
|
|
}
|
|
|
|
if (window->app->destroyRequested != 0) {
|
|
return KS_GPU_WINDOW_EVENT_EXIT;
|
|
}
|
|
if (windowWasActive != window->windowActive) {
|
|
return (window->windowActive) ? KS_GPU_WINDOW_EVENT_ACTIVATED : KS_GPU_WINDOW_EVENT_DEACTIVATED;
|
|
}
|
|
return KS_GPU_WINDOW_EVENT_NONE;
|
|
}
|
|
|
|
#endif
|
|
|
|
void ksGpuWindow_SwapInterval(ksGpuWindow *window, int swapInterval) {
|
|
if (swapInterval != window->windowSwapInterval) {
|
|
#if defined(OS_WINDOWS)
|
|
wglSwapIntervalEXT(swapInterval);
|
|
#elif defined(OS_LINUX_XLIB)
|
|
glXSwapIntervalEXT(window->context.xDisplay, window->xWindow, swapInterval);
|
|
#elif defined(OS_LINUX_XCB)
|
|
xcb_dri2_swap_interval(window->context.connection, window->context.glxDrawable, swapInterval);
|
|
#elif defined(OS_LINUX_XCB_GLX)
|
|
glXSwapIntervalEXT(window->context.xDisplay, window->glxWindow, swapInterval);
|
|
#elif defined(OS_APPLE_MACOS)
|
|
CGLSetParameter(window->context.cglContext, kCGLCPSwapInterval, &swapInterval);
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
EGL(eglSwapInterval(window->context.display, swapInterval));
|
|
#endif
|
|
window->windowSwapInterval = swapInterval;
|
|
}
|
|
}
|
|
|
|
void ksGpuWindow_SwapBuffers(ksGpuWindow *window) {
|
|
#if defined(OS_WINDOWS)
|
|
SwapBuffers(window->context.hDC);
|
|
#elif defined(OS_LINUX_XLIB)
|
|
glXSwapBuffers(window->context.xDisplay, window->xWindow);
|
|
#elif defined(OS_LINUX_XCB)
|
|
xcb_glx_swap_buffers(window->context.connection, window->context.glxContextTag, window->glxWindow);
|
|
#elif defined(OS_LINUX_XCB_GLX)
|
|
glXSwapBuffers(window->context.xDisplay, window->glxWindow);
|
|
#elif defined(OS_APPLE_MACOS)
|
|
CGLFlushDrawable(window->context.cglContext);
|
|
#elif defined(OS_ANDROID) || defined(OS_LINUX_WAYLAND)
|
|
EGL(eglSwapBuffers(window->context.display, window->context.mainSurface));
|
|
#endif
|
|
|
|
ksNanoseconds newTimeNanoseconds = GetTimeNanoseconds();
|
|
|
|
// Even with smoothing, this is not particularly accurate.
|
|
const float frameTimeNanoseconds = 1000.0f * 1000.0f * 1000.0f / window->windowRefreshRate;
|
|
const float deltaTimeNanoseconds = (float)newTimeNanoseconds - window->lastSwapTime - frameTimeNanoseconds;
|
|
if (fabsf(deltaTimeNanoseconds) < frameTimeNanoseconds * 0.75f) {
|
|
newTimeNanoseconds = (ksNanoseconds)(window->lastSwapTime + frameTimeNanoseconds + 0.025f * deltaTimeNanoseconds);
|
|
}
|
|
// const float smoothDeltaNanoseconds = (float)( newTimeNanoseconds - window->lastSwapTime );
|
|
// Print( "frame delta = %1.3f (error = %1.3f)\n", smoothDeltaNanoseconds * 1e-6f,
|
|
// ( smoothDeltaNanoseconds - frameTimeNanoseconds ) * 1e-6f );
|
|
window->lastSwapTime = newTimeNanoseconds;
|
|
}
|
|
|
|
ksNanoseconds ksGpuWindow_GetNextSwapTimeNanoseconds(ksGpuWindow *window) {
|
|
const float frameTimeNanoseconds = 1000.0f * 1000.0f * 1000.0f / window->windowRefreshRate;
|
|
return window->lastSwapTime + (ksNanoseconds)(frameTimeNanoseconds);
|
|
}
|
|
|
|
ksNanoseconds ksGpuWindow_GetFrameTimeNanoseconds(ksGpuWindow *window) {
|
|
const float frameTimeNanoseconds = 1000.0f * 1000.0f * 1000.0f / window->windowRefreshRate;
|
|
return (ksNanoseconds)(frameTimeNanoseconds);
|
|
}
|
|
|
|
void ksGpuWindow_DelayBeforeSwap(ksGpuWindow *window, const ksNanoseconds delay) {
|
|
UNUSED_PARM(window);
|
|
UNUSED_PARM(delay);
|
|
|
|
// FIXME: this appears to not only stall the calling context but also other contexts.
|
|
/*
|
|
#if defined( OS_WINDOWS )
|
|
if ( wglDelayBeforeSwapNV != NULL )
|
|
{
|
|
wglDelayBeforeSwapNV( window->hDC, delay * 1e-6f );
|
|
}
|
|
#elif defined( OS_LINUX_XLIB )
|
|
if ( glXDelayBeforeSwapNV != NULL )
|
|
{
|
|
glXDelayBeforeSwapNV( window->hDC, delay * 1e-6f );
|
|
}
|
|
#endif
|
|
*/
|
|
}
|
|
|
|
static bool ksGpuWindowInput_ConsumeKeyboardKey(ksGpuWindowInput *input, const ksKeyboardKey key) {
|
|
if (input->keyInput[key]) {
|
|
input->keyInput[key] = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool ksGpuWindowInput_ConsumeMouseButton(ksGpuWindowInput *input, const ksMouseButton button) {
|
|
if (input->mouseInput[button]) {
|
|
input->mouseInput[button] = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool ksGpuWindowInput_CheckKeyboardKey(ksGpuWindowInput *input, const ksKeyboardKey key) {
|
|
return (input->keyInput[key] != false);
|
|
}
|
|
|
|
/*
|
|
================================================================================================================================
|
|
|
|
GPU timer.
|
|
|
|
================================================================================================================================
|
|
*/
|
|
|
|
void ksGpuTimer_Create(ksGpuContext *context, ksGpuTimer *timer) {
|
|
UNUSED_PARM(context);
|
|
|
|
if (glExtensions.timer_query) {
|
|
GL(glGenQueries(KS_GPU_TIMER_FRAMES_DELAYED, timer->beginQueries));
|
|
GL(glGenQueries(KS_GPU_TIMER_FRAMES_DELAYED, timer->endQueries));
|
|
timer->queryIndex = 0;
|
|
timer->gpuTime = 0;
|
|
}
|
|
}
|
|
|
|
void ksGpuTimer_Destroy(ksGpuContext *context, ksGpuTimer *timer) {
|
|
UNUSED_PARM(context);
|
|
|
|
if (glExtensions.timer_query) {
|
|
GL(glDeleteQueries(KS_GPU_TIMER_FRAMES_DELAYED, timer->beginQueries));
|
|
GL(glDeleteQueries(KS_GPU_TIMER_FRAMES_DELAYED, timer->endQueries));
|
|
}
|
|
}
|
|
|
|
ksNanoseconds ksGpuTimer_GetNanoseconds(ksGpuTimer *timer) {
|
|
if (glExtensions.timer_query) {
|
|
return timer->gpuTime;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|