diff --git a/.gitmodules b/.gitmodules index b63750f8..bcb8fea4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/kvm-guest-drivers-windows"] path = vendor/kvm-guest-drivers-windows url = https://github.com/virtio-win/kvm-guest-drivers-windows.git +[submodule "client/cimgui"] + path = client/cimgui + url = https://github.com/Extrawurst/cimgui.git diff --git a/client/.gitignore b/client/.gitignore index 08cb1ee8..6b509618 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,2 +1,3 @@ bin/ +.build/ *.swp diff --git a/client/Makefile b/client/Makefile index 7d14c950..7023cb4d 100644 --- a/client/Makefile +++ b/client/Makefile @@ -2,6 +2,10 @@ BINARY = looking-glass-client CFLAGS = -g -Og -std=gnu99 -march=native -Wall -Werror -I./ -I../common -DDEBUG LDFLAGS = -lrt +CFLAGS += -ffast-math +CFLAGS += -fdata-sections -ffunction-sections +LDFLAGS += -Wl,--gc-sections + LIBS = sdl2 SDL2_ttf gl glu libssl openssl spice-protocol CFLAGS += $(shell pkg-config --cflags $(LIBS)) LDFLAGS += $(shell pkg-config --libs $(LIBS)) @@ -10,19 +14,21 @@ BIN ?= bin OBJS = main.o \ spice/spice.o \ - ivshmem/ivshmem.o + ivshmem/ivshmem.o \ + renderers/basic.o \ + renderers/opengl.o BUILD_OBJS = $(foreach obj,$(OBJS),$(BUILD)/$(obj)) -all: $(BINARY) +all: $(BIN)/$(BINARY) $(BUILD)/%.o: %.c @mkdir -p $(dir $@) - gcc -c $(CFLAGS) -o $@ $< $(LDFLAGS) + gcc -c $(CFLAGS) -o $@ $< -$(BINARY): $(BUILD_OBJS) +$(BIN)/$(BINARY): $(BUILD_OBJS) @mkdir -p $(dir $(BIN)/$@) - gcc $(CFLAGS) -o $(BIN)/$(BINARY) $(BUILD_OBJS) $(LDFLAGS) + gcc -o $(BIN)/$(BINARY) $(BUILD_OBJS) $(LDFLAGS) clean: rm -rf $(BUILD) $(BIN) diff --git a/client/lg-renderer.h b/client/lg-renderer.h new file mode 100644 index 00000000..ee7feb75 --- /dev/null +++ b/client/lg-renderer.h @@ -0,0 +1,72 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017 Geoffrey McRae + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma once +#include +#include + +#include + +#define IS_LG_RENDERER_VALID(x) \ + ((x)->get_name && \ + (x)->initialize && \ + (x)->deinitialize && \ + (x)->is_compatible && \ + (x)->render) + +typedef struct LG_RendererParams +{ + SDL_Window * window; + SDL_Renderer * renderer; +} +LG_RendererParams; + +typedef struct LG_RendererFormat +{ + unsigned int width; // image width + unsigned int height; // image height + unsigned int stride; // scanline width + unsigned int pitch; // scanline bytes + unsigned int bpp; // bits per pixel +} +LG_RendererFormat; + +typedef struct LG_RendererRect +{ + int x; + int y; + unsigned int w; + unsigned int h; +} +LG_RendererRect; + +typedef const char * (* LG_RendererGetName )(); +typedef bool (* LG_RendererInitialize )(void ** opaque, const LG_RendererParams params, const LG_RendererFormat format); +typedef void (* LG_RendererDeInitialize)(void * opaque); +typedef bool (* LG_RendererIsCompatible)(void * opaque, const LG_RendererFormat format); +typedef bool (* LG_RendererRender )(void * opaque, const LG_RendererRect destRect, const uint8_t * data, bool resample); + +typedef struct LG_Renderer +{ + LG_RendererGetName get_name; + LG_RendererInitialize initialize; + LG_RendererDeInitialize deinitialize; + LG_RendererIsCompatible is_compatible; + LG_RendererRender render; +} +LG_Renderer; \ No newline at end of file diff --git a/client/lg-renderers.h b/client/lg-renderers.h new file mode 100644 index 00000000..aebaf0e9 --- /dev/null +++ b/client/lg-renderers.h @@ -0,0 +1,30 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017 Geoffrey McRae + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#pragma once +#include "lg-renderer.h" + +extern const LG_Renderer LGR_OpenGL; +extern const LG_Renderer LGR_Basic; + +const LG_Renderer * LG_Renderers[] = +{ + &LGR_OpenGL, + &LGR_Basic, + NULL // end of array sentinal +}; \ No newline at end of file diff --git a/client/main.c b/client/main.c index 4e101261..6c6f54cd 100644 --- a/client/main.c +++ b/client/main.c @@ -40,6 +40,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include "spice/spice.h" #include "kb.h" +#include "lg-renderers.h" + #define VBO_BUFFERS 2 struct AppState @@ -49,14 +51,15 @@ struct AppState bool running; bool started; - TTF_Font *font; - SDL_Rect srcRect, dstRect; - float scaleX, scaleY; + TTF_Font *font; + SDL_Rect srcRect; + LG_RendererRect dstRect; + float scaleX, scaleY; - SDL_Window * window; - SDL_Renderer * renderer; + SDL_Window * window; + SDL_Renderer * renderer; struct KVMFRHeader * shm; - unsigned int shmSize; + unsigned int shmSize; }; struct AppParams @@ -184,30 +187,14 @@ inline bool waitGuest() int renderThread(void * unused) { - struct KVMFRHeader header; - struct KVMFRHeader newHeader; - SDL_Texture *texture = NULL; - GLuint vboID[VBO_BUFFERS]; - GLuint intFormat = 0; - GLuint vboFormat = 0; - GLuint vboTex[VBO_BUFFERS]; - unsigned int texIndex = 0; - unsigned int texSize = 0; - uint8_t *pixels = (uint8_t*)state.shm; - uint8_t *texPixels[VBO_BUFFERS]; - - unsigned int ticks = SDL_GetTicks(); - unsigned int frameCount = 0; - SDL_Texture *textTexture = NULL; - SDL_Rect textRect = {0, 0, 0, 0}; - - memset(&header , 0, sizeof(struct KVMFRHeader)); - memset(&vboID , 0, sizeof(vboID)); - memset(&vboTex , 0, sizeof(vboTex)); - memset(&texPixels, 0, sizeof(texPixels)); - - // initial guest kick to get things started - ivshmem_kick_irq(state.shm->guestID, 0); + bool error = false; + struct KVMFRHeader header; + const LG_Renderer * lgr = NULL; + void * lgrData; + unsigned int lastTicks = SDL_GetTicks(); + unsigned int frameCount = 0, lastFrameCount = 0; + SDL_Texture * textTexture = NULL; + SDL_Rect textRect; while(state.running) { @@ -215,145 +202,112 @@ int renderThread(void * unused) if (!waitGuest()) break; - memcpy(&newHeader, state.shm, sizeof(struct KVMFRHeader)); + // we must take a copy of the header, both to let the guest advance and to + // prevent the contained arguments being abused to overflow buffers + memcpy(&header, state.shm, sizeof(struct KVMFRHeader)); + ivshmem_kick_irq(header.guestID, 0); - // ensure the header magic is valid, this will help prevent crash out when the memory hasn't yet been initialized + // check the header's magic and version are valid if ( - memcmp(newHeader.magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0 || - newHeader.version != KVMFR_HEADER_VERSION + memcmp(header.magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0 || + header.version != KVMFR_HEADER_VERSION ) { usleep(1000); continue; } - // if the header is invalid or it has changed - if (!areFormatsSame(header, newHeader)) + // setup the renderer format with the frame format details + LG_RendererFormat lgrFormat; + lgrFormat.width = header.width; + lgrFormat.height = header.height; + lgrFormat.stride = header.stride; + + switch(header.frameType) { - if (state.hasBufferStorage) + case FRAME_TYPE_ARGB: + lgrFormat.pitch = header.stride * 4; + lgrFormat.bpp = 32; + break; + + case FRAME_TYPE_RGB: + lgrFormat.pitch = header.stride * 3; + lgrFormat.bpp = 24; + break; + + default: + DEBUG_ERROR("Unsupported frameType"); + error = true; + break; + } + + if (error) + break; + + // check the header's dataPos is sane + const size_t dataSize = lgrFormat.height * lgrFormat.pitch; + if (header.dataPos + dataSize > state.shmSize) + { + DEBUG_ERROR("The guest sent an invalid dataPos"); + break; + } + + // check if we have a compatible renderer + if (!lgr || !lgr->is_compatible(lgrData, lgrFormat)) + { + LG_RendererParams lgrParams; + lgrParams.window = state.window; + lgrParams.renderer = state.renderer; + + DEBUG_INFO("Data Format: w=%u, h=%u, s=%u, p=%u, bpp=%u", + lgrFormat.width, lgrFormat.height, lgrFormat.stride, lgrFormat.pitch, lgrFormat.bpp); + + // first try to reinitialize any existing renderer + if (lgr) { - if (vboID[0]) + lgr->deinitialize(lgrData); + if (lgr->initialize(&lgrData, lgrParams, lgrFormat)) { - if (vboTex[0]) + DEBUG_INFO("Reinitialized %s", lgr->get_name()); + } + else + { + DEBUG_ERROR("Failed to reinitialize %s, trying other renderers", lgr->get_name()); + lgr->deinitialize(lgrData); + lgr = NULL; + } + } + + if (!lgr) + { + // probe for a a suitable renderer + for(const LG_Renderer **r = &LG_Renderers[0]; *r; ++r) + { + if (!IS_LG_RENDERER_VALID(*r)) { - glDeleteTextures(VBO_BUFFERS, vboTex); - memset(vboTex, 0, sizeof(vboTex)); + DEBUG_ERROR("FIXME: Renderer %d is invalid, skpping", (int)(r - &LG_Renderers[0])); + continue; } - glUnmapBuffer(GL_TEXTURE_BUFFER); - glDeleteBuffers(VBO_BUFFERS, vboID); - memset(vboID, 0, sizeof(vboID)); - } - } - else - { - if (texture) - { - SDL_DestroyTexture(texture); - texture = NULL; - } - } - - Uint32 sdlFormat; - unsigned int bpp; - switch(newHeader.frameType) - { - case FRAME_TYPE_ARGB: - sdlFormat = SDL_PIXELFORMAT_ARGB8888; - bpp = 4; - intFormat = GL_RGBA8; - vboFormat = GL_BGRA; - break; - - case FRAME_TYPE_RGB: - sdlFormat = SDL_PIXELFORMAT_RGB24; - bpp = 3; - intFormat = GL_RGB8; - vboFormat = GL_BGR; - break; - - default: - header.frameType = FRAME_TYPE_INVALID; - continue; - } - - // update the window size and create the render texture - if (params.autoResize) - { - SDL_SetWindowSize(state.window, newHeader.width, newHeader.height); - if (params.center) - SDL_SetWindowPosition(state.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - } - - if (state.hasBufferStorage) - { - // calculate the texture size in bytes - texSize = newHeader.height * newHeader.stride * bpp; - - // ensure the size makes sense - if (newHeader.dataPos + texSize > state.shmSize) - { - DEBUG_ERROR("The guest sent an invalid dataPos"); - break; - } - - // setup two buffers so we don't have to use fences - glGenBuffers(VBO_BUFFERS, vboID); - for (int i = 0; i < VBO_BUFFERS; ++i) - { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, vboID[i]); - glBufferStorage(GL_PIXEL_UNPACK_BUFFER, texSize, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); - texPixels[i] = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texSize, - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); - if (!texPixels[i]) + lgrData = NULL; + if (!(*r)->initialize(&lgrData, lgrParams, lgrFormat)) { - DEBUG_ERROR("Failed to map buffer range, turning off buffer storage"); - state.hasBufferStorage = false; - break; + (*r)->deinitialize(lgrData); + continue; } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - } - if (!state.hasBufferStorage) - { - texIndex = 0; - glDeleteBuffers(VBO_BUFFERS, vboID); - memset(vboID, 0, sizeof(vboID)); - continue; - } - - // create the textures - glGenTextures(VBO_BUFFERS, vboTex); - for (int i = 0; i < VBO_BUFFERS; ++i) - { - glBindTexture(GL_TEXTURE_2D, vboTex[i]); - glTexImage2D( - GL_TEXTURE_2D, - 0, - intFormat, - newHeader.width, newHeader.height, - 0, - vboFormat, - GL_UNSIGNED_BYTE, - (void*)0 - ); - glBindTexture(GL_TEXTURE_2D, 0); - } - } - else - { - texture = SDL_CreateTexture(state.renderer, sdlFormat, SDL_TEXTUREACCESS_STREAMING, newHeader.width, newHeader.height); - if (!texture) - { - DEBUG_ERROR("Failed to create a texture"); + lgr = *r; + DEBUG_INFO("Initialized %s", (*r)->get_name()); break; } + + if (!lgr) + { + DEBUG_INFO("Unable to find a suitable renderer"); + return -1; + } } - ticks = SDL_GetTicks(); - frameCount = 0; - - memcpy(&header, &newHeader, sizeof(header)); state.srcRect.x = 0; state.srcRect.y = 0; state.srcRect.w = header.width; @@ -361,170 +315,91 @@ int renderThread(void * unused) updatePositionInfo(); } - //beyond this point DO NOT use state.shm for security - - // final sanity checks on the data presented by the guest - // this is critical as the guest could overflow this buffer to - // try to take control of the host - if (newHeader.dataPos + texSize > state.shmSize) + if (!lgr->render( + lgrData, + state.dstRect, + (uint8_t *)state.shm + header.dataPos, + params.useMipmap + )) { - DEBUG_ERROR("The guest sent an invalid dataPos"); + DEBUG_ERROR("Failed to render the frame"); break; } - SDL_RenderClear(state.renderer); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - - if (state.hasBufferStorage) + ++frameCount; + if (!params.showFPS) { - // copy the buffer to the texture and let the guest advance - memcpySSE(texPixels[texIndex], pixels + newHeader.dataPos, texSize); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, vboID[texIndex]); - glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texSize); - - // bind the texture and update it - glBindTexture(GL_TEXTURE_2D , vboTex[texIndex]); - glPixelStorei(GL_UNPACK_ALIGNMENT , 1 ); - glPixelStorei(GL_UNPACK_ROW_LENGTH , header.width ); - glTexSubImage2D( - GL_TEXTURE_2D, - 0, - 0, 0, - header.width, header.height, - vboFormat, - GL_UNSIGNED_BYTE, - (void*)0 - ); - if (params.useMipmap) - glGenerateMipmap(GL_TEXTURE_2D); - - // unbind the buffer - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - // configure the texture - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (params.useMipmap) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - else - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } - - // draw the screen - glEnable(GL_TEXTURE_2D); - glBegin(GL_TRIANGLE_STRIP); - glTexCoord2f(0.0f, 0.0f); glVertex2i(state.dstRect.x , state.dstRect.y ); - glTexCoord2f(1.0f, 0.0f); glVertex2i(state.dstRect.x + state.dstRect.w, state.dstRect.y ); - glTexCoord2f(0.0f, 1.0f); glVertex2i(state.dstRect.x , state.dstRect.y + state.dstRect.h); - glTexCoord2f(1.0f, 1.0f); glVertex2i(state.dstRect.x + state.dstRect.w, state.dstRect.y + state.dstRect.h); - glEnd(); - glDisable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); - - // update our texture index - if (++texIndex == VBO_BUFFERS) - texIndex = 0; - } - else - { - int pitch; - if (SDL_LockTexture(texture, NULL, (void**)&texPixels[0], &pitch) != 0) - { - DEBUG_ERROR("Failed to lock the texture for update"); - break; - } - texSize = header.height * pitch; - - // copy the buffer to the texture and let the guest advance - memcpySSE(texPixels[texIndex], pixels + newHeader.dataPos, texSize); - - SDL_UnlockTexture(texture); - SDL_RenderCopy(state.renderer, texture, NULL, &state.dstRect); + SDL_RenderPresent(state.renderer); + continue; } - if (params.showFPS) + // for now render the frame counter here, we really should + // move this into the renderers though. + if (frameCount % 10 == 0) { - if (++frameCount == 10) - { - SDL_Surface *textSurface = NULL; - if (textTexture) - { - SDL_DestroyTexture(textTexture); - textTexture = NULL; - } - const unsigned int time = SDL_GetTicks(); - const float avgFPS = (float)frameCount / ((time - ticks) / 1000.0f); - char strFPS[12]; - snprintf(strFPS, sizeof(strFPS), "FPS: %6.2f", avgFPS); - SDL_Color color = {0xff, 0xff, 0xff}; - if (!(textSurface = TTF_RenderText_Blended(state.font, strFPS, color))) - { - DEBUG_ERROR("Failed to render text"); - break; - } - - textRect.x = 5; - textRect.y = 5; - textRect.w = textSurface->w; - textRect.h = textSurface->h; - - textTexture = SDL_CreateTextureFromSurface(state.renderer, textSurface); - SDL_SetTextureBlendMode(textTexture, SDL_BLENDMODE_BLEND); - SDL_FreeSurface(textSurface); - - frameCount = 0; - ticks = time; - } - + SDL_Surface *textSurface = NULL; if (textTexture) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glColor4f(0.0f, 0.0f, 1.0f, 0.5f); - glBegin(GL_TRIANGLE_STRIP); - glVertex2i(textRect.x , textRect.y ); - glVertex2i(textRect.x + textRect.w, textRect.y ); - glVertex2i(textRect.x , textRect.y + textRect.h); - glVertex2i(textRect.x + textRect.w, textRect.y + textRect.h); - glEnd(); - - - float tw, th; - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - SDL_GL_BindTexture(textTexture, &tw, &th); - glBegin(GL_TRIANGLE_STRIP); - glTexCoord2f(0.0f, 0.0f); glVertex2i(textRect.x , textRect.y ); - glTexCoord2f(tw , 0.0f); glVertex2i(textRect.x + textRect.w, textRect.y ); - glTexCoord2f(0.0f, th ); glVertex2i(textRect.x , textRect.y + textRect.h); - glTexCoord2f(tw , th ); glVertex2i(textRect.x + textRect.w, textRect.y + textRect.h); - glEnd(); - glDisable(GL_BLEND); - SDL_GL_UnbindTexture(textTexture); + SDL_DestroyTexture(textTexture); + textTexture = NULL; } + + const unsigned int ticks = SDL_GetTicks(); + const float avgFPS = (float)(frameCount - lastFrameCount) / ((ticks - lastTicks) / 1000.0f); + char strFPS[12]; + snprintf(strFPS, sizeof(strFPS), "FPS: %6.2f", avgFPS); + SDL_Color color = {0xff, 0xff, 0xff}; + if (!(textSurface = TTF_RenderText_Blended(state.font, strFPS, color))) + { + DEBUG_ERROR("Failed to render text"); + break; + } + + textRect.x = 5; + textRect.y = 5; + textRect.w = textSurface->w; + textRect.h = textSurface->h; + + textTexture = SDL_CreateTextureFromSurface(state.renderer, textSurface); + SDL_SetTextureBlendMode(textTexture, SDL_BLENDMODE_BLEND); + SDL_FreeSurface(textSurface); + + lastTicks = ticks; + lastFrameCount = frameCount; + } + + if (textTexture) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glColor4f(0.0f, 0.0f, 1.0f, 0.5f); + glBegin(GL_TRIANGLE_STRIP); + glVertex2i(textRect.x , textRect.y ); + glVertex2i(textRect.x + textRect.w, textRect.y ); + glVertex2i(textRect.x , textRect.y + textRect.h); + glVertex2i(textRect.x + textRect.w, textRect.y + textRect.h); + glEnd(); + + + float tw, th; + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + SDL_GL_BindTexture(textTexture, &tw, &th); + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0.0f, 0.0f); glVertex2i(textRect.x , textRect.y ); + glTexCoord2f(tw , 0.0f); glVertex2i(textRect.x + textRect.w, textRect.y ); + glTexCoord2f(0.0f, th ); glVertex2i(textRect.x , textRect.y + textRect.h); + glTexCoord2f(tw , th ); glVertex2i(textRect.x + textRect.w, textRect.y + textRect.h); + glEnd(); + glDisable(GL_BLEND); + SDL_GL_UnbindTexture(textTexture); } SDL_RenderPresent(state.renderer); - ivshmem_kick_irq(newHeader.guestID, 0); - - state.started = true; } - state.running = false; - if (state.hasBufferStorage) - { - glDeleteTextures(VBO_BUFFERS, vboTex); - glUnmapBuffer (GL_TEXTURE_BUFFER ); - glDeleteBuffers (VBO_BUFFERS, vboID ); - } - else - if (texture) - SDL_DestroyTexture(texture); + if (lgr) + lgr->deinitialize(lgrData); return 0; } @@ -833,14 +708,10 @@ int run() (params.vsync ? SDL_RENDERER_PRESENTVSYNC : 0) ); - if (params.useBufferStorage) + if (!state.renderer) { - const GLubyte * extensions = glGetString(GL_EXTENSIONS); - if (gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions)) - { - DEBUG_INFO("Using GL_ARB_buffer_storage"); - state.hasBufferStorage = true; - } + DEBUG_ERROR("failed to create renderer"); + return -1; } if (params.vsync) @@ -852,10 +723,14 @@ int run() else SDL_GL_SetSwapInterval(0); - if (!state.renderer) + if (params.useBufferStorage) { - DEBUG_ERROR("failed to create window"); - return -1; + const GLubyte * extensions = glGetString(GL_EXTENSIONS); + if (gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions)) + { + DEBUG_INFO("Using GL_ARB_buffer_storage"); + state.hasBufferStorage = true; + } } int shm_fd = 0; diff --git a/client/renderers/basic.c b/client/renderers/basic.c new file mode 100644 index 00000000..f1046179 --- /dev/null +++ b/client/renderers/basic.c @@ -0,0 +1,146 @@ +#include "lg-renderer.h" + +#include +#include +#include + +#include "debug.h" +#include "memcpySSE.h" + +struct LGR_Basic +{ + bool initialized; + LG_RendererFormat format; + size_t texSize; + size_t dataWidth; + SDL_Renderer * renderer; + SDL_Texture * texture; +}; + +const char * lgr_basic_get_name() +{ + return "Basic"; +} + +bool lgr_basic_initialize(void ** opaque, const LG_RendererParams params, const LG_RendererFormat format) +{ + // create our local storage + *opaque = malloc(sizeof(struct LGR_Basic)); + if (!*opaque) + { + DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct LGR_Basic)); + return false; + } + memset(*opaque, 0, sizeof(struct LGR_Basic)); + struct LGR_Basic * this = (struct LGR_Basic *)*opaque; + + Uint32 sdlFormat; + switch(format.bpp) + { + case 24: + sdlFormat = SDL_PIXELFORMAT_RGB24; + break; + + case 32: + sdlFormat = SDL_PIXELFORMAT_ARGB8888; + break; + + default: + DEBUG_ERROR("Unsupported bpp"); + return false; + } + + // calculate the texture size in bytes + this->texSize = format.height * format.pitch; + + // create the target texture + this->texture = SDL_CreateTexture( + params.renderer, + sdlFormat, + SDL_TEXTUREACCESS_STREAMING, + format.width, + format.height + ); + + if (!this->texture) + { + DEBUG_ERROR("SDL_CreateTexture failed"); + return false; + } + + + memcpy(&this->format, &format, sizeof(LG_RendererFormat)); + this->renderer = params.renderer; + this->dataWidth = this->format.width * (this->format.bpp / 8); + this->initialized = true; + return true; +} + +void lgr_basic_deinitialize(void * opaque) +{ + struct LGR_Basic * this = (struct LGR_Basic *)opaque; + if (!this) + return; + + if (this->texture) + SDL_DestroyTexture(this->texture); + + free(this); +} + +bool lgr_basic_is_compatible(void * opaque, const LG_RendererFormat format) +{ + const struct LGR_Basic * this = (struct LGR_Basic *)opaque; + if (!this || !this->initialized) + return false; + + return (memcmp(&this->format, &format, sizeof(LG_RendererFormat)) == 0); +} + +bool lgr_basic_render(void * opaque, const LG_RendererRect destRect, const uint8_t * data, bool resample) +{ + struct LGR_Basic * this = (struct LGR_Basic *)opaque; + if (!this || !this->initialized) + return false; + + int pitch; + uint8_t * dest; + + if (SDL_LockTexture(this->texture, NULL, (void**)&dest, &pitch) != 0) + { + DEBUG_ERROR("Failed to lock the texture for update"); + return false; + } + + if (pitch == this->format.pitch) + memcpySSE(dest, data, this->texSize); + else + { + for(unsigned int y = 0; y < this->format.height; ++y) + { + memcpySSE(dest, data, this->dataWidth); + dest += pitch; + data += this->format.pitch; + } + } + + SDL_Rect rect; + rect.x = destRect.x; + rect.y = destRect.y; + rect.w = destRect.w; + rect.h = destRect.h; + + SDL_UnlockTexture(this->texture); + SDL_RenderCopy(this->renderer, this->texture, NULL, &rect); + + return true; +} + +const LG_Renderer LGR_Basic = +{ + .get_name = lgr_basic_get_name, + .initialize = lgr_basic_initialize, + .deinitialize = lgr_basic_deinitialize, + .is_compatible = lgr_basic_is_compatible, + .render = lgr_basic_render +}; \ No newline at end of file diff --git a/client/renderers/opengl.c b/client/renderers/opengl.c new file mode 100644 index 00000000..1162ba13 --- /dev/null +++ b/client/renderers/opengl.c @@ -0,0 +1,255 @@ +#include "lg-renderer.h" +#include +#include + +#define GL_GLEXT_PROTOTYPES +#include +#include + +#include "debug.h" +#include "memcpySSE.h" + +#define VBO_BUFFERS 2 + +struct LGR_OpenGL +{ + bool initialized; + LG_RendererFormat format; + GLuint intFormat; + GLuint vboFormat; + size_t texSize; + + bool hasBuffers; + GLuint vboID[VBO_BUFFERS]; + uint8_t * texPixels[VBO_BUFFERS]; + int texIndex; + + bool hasTextures; + GLuint vboTex[VBO_BUFFERS]; +}; + +bool lgr_opengl_check_error(const char * name) +{ + GLenum error = glGetError(); + if (error == GL_NO_ERROR) + return false; + + const GLubyte * errStr = gluErrorString(error); + DEBUG_ERROR("%s = %d (%s)", name, error, errStr); + return true; +} + +const char * lgr_opengl_get_name() +{ + return "OpenGL"; +} + +bool lgr_opengl_initialize(void ** opaque, const LG_RendererParams params, const LG_RendererFormat format) +{ + // check if the GPU supports GL_ARB_buffer_storage first + // there is no advantage to this renderer if it is not present. + const GLubyte * extensions = glGetString(GL_EXTENSIONS); + if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions)) + { + DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage"); + return false; + } + + // create our local storage + *opaque = malloc(sizeof(struct LGR_OpenGL)); + if (!*opaque) + { + DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct LGR_OpenGL)); + return false; + } + memset(*opaque, 0, sizeof(struct LGR_OpenGL)); + struct LGR_OpenGL * this = (struct LGR_OpenGL *)*opaque; + + // assume 24 and 32 bit formats are RGB and RGBA + switch(format.bpp) + { + case 24: + this->intFormat = GL_RGB8; + this->vboFormat = GL_BGR; + break; + + case 32: + this->intFormat = GL_RGBA8; + this->vboFormat = GL_BGRA; + break; + + default: + DEBUG_INFO("%d bpp not supported", format.bpp); + return false; + } + + // calculate the texture size in bytes + this->texSize = format.height * format.pitch; + + // generate the pixel unpack buffers + glGenBuffers(VBO_BUFFERS, this->vboID); + if (lgr_opengl_check_error("glGenBuffers")) + return false; + this->hasBuffers = true; + + // persistant bind the buffers + for (int i = 0; i < VBO_BUFFERS; ++i) + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]); + if (lgr_opengl_check_error("glBindBuffer")) + return false; + + glBufferStorage(GL_PIXEL_UNPACK_BUFFER, this->texSize, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); + if (lgr_opengl_check_error("glBufferStorage")) + return false; + + this->texPixels[i] = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, this->texSize, + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); + if (lgr_opengl_check_error("glMapBufferRange")) + return false; + + if (!this->texPixels[i]) + { + DEBUG_ERROR("Failed to map the buffer range"); + return false; + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + // create the textures + glGenTextures(VBO_BUFFERS, this->vboTex); + if (lgr_opengl_check_error("glGenTextures")) + return false; + this->hasTextures = true; + + // bind the textures to the unpack buffers + for (int i = 0; i < VBO_BUFFERS; ++i) + { + glBindTexture(GL_TEXTURE_2D, this->vboTex[i]); + if (lgr_opengl_check_error("glBindTexture")) + return false; + + glTexImage2D( + GL_TEXTURE_2D, + 0, + this->intFormat, + format.width, format.height, + 0, + this->vboFormat, + GL_UNSIGNED_BYTE, + (void*)0 + ); + + if (lgr_opengl_check_error("glTexImage2D")) + return false; + } + + glEnable(GL_TEXTURE_2D); + + // copy the format into the local storage + memcpy(&this->format, &format, sizeof(LG_RendererFormat)); + this->initialized = true; + return true; +} + +void lgr_opengl_deinitialize(void * opaque) +{ + struct LGR_OpenGL * this = (struct LGR_OpenGL *)opaque; + if (!this) + return; + + if (this->hasTextures) + glDeleteTextures(VBO_BUFFERS, this->vboTex); + + if (this->hasBuffers) + glDeleteBuffers(VBO_BUFFERS, this->vboID); + + free(this); +} + +bool lgr_opengl_is_compatible(void * opaque, const LG_RendererFormat format) +{ + const struct LGR_OpenGL * this = (struct LGR_OpenGL *)opaque; + if (!this || !this->initialized) + return false; + + return (memcmp(&this->format, &format, sizeof(LG_RendererFormat)) == 0); +} + +bool lgr_opengl_render(void * opaque, const LG_RendererRect destRect, const uint8_t * data, bool resample) +{ + struct LGR_OpenGL * this = (struct LGR_OpenGL *)opaque; + if (!this || !this->initialized) + return false; + + glClear(GL_COLOR_BUFFER_BIT); + + // copy the buffer to the texture + memcpySSE(this->texPixels[this->texIndex], data, this->texSize); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]); + glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, this->texSize); + + // bind the texture and update it + glBindTexture(GL_TEXTURE_2D , this->vboTex[this->texIndex]); + glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); + glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.width ); + glTexSubImage2D( + GL_TEXTURE_2D, + 0, + 0, 0, + this->format.width, + this->format.height, + this->vboFormat, + GL_UNSIGNED_BYTE, + (void*)0 + ); + + const bool mipmap = resample && ( + (this->format.width != destRect.w) || + (this->format.height != destRect.h)); + + // unbind the buffer + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + // configure the texture + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (mipmap) + { + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + // draw the screen + glEnable(GL_TEXTURE_2D); + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0.0f, 0.0f); glVertex2i(destRect.x , destRect.y ); + glTexCoord2f(1.0f, 0.0f); glVertex2i(destRect.x + destRect.w, destRect.y ); + glTexCoord2f(0.0f, 1.0f); glVertex2i(destRect.x , destRect.y + destRect.h); + glTexCoord2f(1.0f, 1.0f); glVertex2i(destRect.x + destRect.w, destRect.y + destRect.h); + glEnd(); + glDisable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, 0); + + if (++this->texIndex == VBO_BUFFERS) + this->texIndex = 0; + + return true; +} + +const LG_Renderer LGR_OpenGL = +{ + .get_name = lgr_opengl_get_name, + .initialize = lgr_opengl_initialize, + .deinitialize = lgr_opengl_deinitialize, + .is_compatible = lgr_opengl_is_compatible, + .render = lgr_opengl_render +}; \ No newline at end of file