WIP: SDL/OpenGL renderer. Make mvedecode use renderer for playback.

This commit is contained in:
2015-04-30 16:16:48 +02:00
parent b9d4048695
commit 7fb5f66091
27 changed files with 1399 additions and 103 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
game/
data/
objs/
tmp/
*~

View File

@@ -1,8 +1,9 @@
CXX=g++
CXXOPTS=-Og -ggdb -fvar-tracking-assignments -Wall -Wextra -pedantic -std=c++14 -march=native -fstack-protector-strong --param=ssp-buffer-size=4 -flto
CXXOPTS=-Og -ggdb -fvar-tracking-assignments -Wall -Wextra -Wno-unused-function -pedantic -std=c++14 -march=native -fstack-protector-strong --param=ssp-buffer-size=4 -flto -I.
LDOPTS=-Wl,--sort-common,--as-needed
render_CXXSRCS ::= render/Renderer.cc render/GlResource.cc
render_CXXSRCS ::= render/Renderer.cc render/GlResource.cc render/ProgramProvider.cc render/renderutil.cc render/Overlay.cc render/VBOManager.cc
game_CXXSRCS ::= game/GSMvePlay.cc
iffexplore_CXXSRCS ::= iffexplore.cc IffFile.cc util.cc exceptions.cc
iffexplore_LIBS ::=
@@ -13,8 +14,8 @@ treexplore_LIBS ::=
font2png_CXXSRCS ::= font2png.cc
font2png_LIBS ::= -lpng
mvedecode_CXXSRCS ::= mvedecode.cc TreFile.cc IffFile.cc util.cc MveDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS)
mvedecode_LIBS ::= -lSDL2
mvedecode_CXXSRCS ::= mvedecode.cc TreFile.cc IffFile.cc util.cc MveDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS) $(game_CXXSRCS)
mvedecode_LIBS ::= -lSDL2 -lglbinding
progs ::= iffexplore treexplore mvedecode

View File

@@ -207,6 +207,76 @@ std::vector<uint8_t> parsePixels_(char const* data, size_t len)
}
}
MveDecoder::Movie MveDecoder::open(size_t branch) const
{
auto& br = branches_.at(branch);
return Movie(br.cbegin(), br.cend(),
*this);
}
MveDecoder::Movie::Movie(std::vector<Shot>::const_iterator shotIt,
std::vector<Shot>::const_iterator shotEnd,
MveDecoder const& mve)
: shotIt_(shotIt), shotEnd_(shotEnd),
frameIt_(shotIt_->VGAs.cbegin()),
audioIt_(shotIt_->AUDIs.cbegin()),
mve_(mve)
{
}
bool MveDecoder::Movie::hasNext() const {
if (shotIt_ == shotEnd_)
return false;
return true;
}
std::tuple<MveDecoder::Frame const&, MveDecoder::Palette const&> MveDecoder::Movie::decodeNext()
{
frame_ = mve_.parseVGA(**frameIt_++, &frame_);
auto& palette = shotIt_->palt;
if (frameIt_ == shotIt_->VGAs.cend()) {
++shotIt_;
if (shotIt_ != shotEnd_) {
frameIt_ = shotIt_->VGAs.cbegin();
audioIt_ = shotIt_->AUDIs.cbegin();
}
}
return std::make_tuple(frame_, palette);
}
bool MveDecoder::Movie::hasAudio() const
{
return (audioIt_ != shotIt_->AUDIs.cend());
}
MveDecoder::Audio MveDecoder::Movie::getNextAudio()
{
MveDecoder::Audio ret;
if (audioIt_ != shotIt_->AUDIs.cend()) {
ret.reserve((shotIt_->AUDIs.cend()-audioIt_)*2940);
for(;audioIt_ != shotIt_->AUDIs.cend();++audioIt_)
std::copy((*audioIt_)->begin(), (*audioIt_)->end(),
std::back_inserter(ret));
}
return ret;
}
unsigned MveDecoder::Movie::getWidth() const
{
return mve_.width_;
}
unsigned MveDecoder::Movie::getHeight() const
{
return mve_.height_;
}
MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* prev) const
{
// Parse header
@@ -242,7 +312,7 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const*
// Interpret command stream to render frame
Frame ret;
ret.pixels.reserve(width_*height_);
ret.reserve(width_*height_);
bool flag = false;
size_t seg2Idx = 0;
@@ -320,12 +390,12 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const*
#endif
if (!prev)
throw FormatException{"Reference to non-existing prev. frame"};
if (ret.pixels.size()+size > prev->pixels.size())
if (ret.size()+size > prev->size())
throw FormatException{"Copy from prev exceeds frame"};
std::copy(prev->pixels.begin()+ret.pixels.size(),
prev->pixels.begin()+ret.pixels.size()+size,
std::back_inserter(ret.pixels));
std::copy(prev->begin()+ret.size(),
prev->begin()+ret.size()+size,
std::back_inserter(ret));
} else {
#ifdef CMDDEBUG_
printf("DataCopy: %u @%lu\n", size, seg4Idx);
@@ -335,7 +405,7 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const*
std::copy(pixels.begin()+seg4Idx,
pixels.begin()+seg4Idx+size,
std::back_inserter(ret.pixels));
std::back_inserter(ret));
seg4Idx += size;
}
} else {
@@ -352,25 +422,25 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const*
const uint8_t mpos = vga.begin()[segOfs[2]+seg3Idx++];
const signed x = sextend(mpos>>4, 4), y = sextend(mpos&0xf,4);
const signed delta = y*width_+x;
if ((delta < 0) && (ret.pixels.size() < static_cast<size_t>(-delta)))
if ((delta < 0) && (ret.size() < static_cast<size_t>(-delta)))
throw FormatException{"Motion vector outside frame"};
const unsigned ref = ret.pixels.size()+delta;
const unsigned ref = ret.size()+delta;
#ifdef CMDDEBUG_
printf("@%lu %.2hhx -> (%d, %d) -> d %d -> %u\n", seg3Idx-1, mpos, x, y, delta, ref);
#endif
if (ref+size > prev->pixels.size())
if (ref+size > prev->size())
throw FormatException{"Copy from prev exceeds frame"};
std::copy(prev->pixels.begin()+ref,
prev->pixels.begin()+ref+size,
std::back_inserter(ret.pixels));
std::copy(prev->begin()+ref,
prev->begin()+ref+size,
std::back_inserter(ret));
flag = false;
}
}
if (ret.pixels.size() != width_*height_)
if (ret.size() != width_*height_)
throw FormatException{"Decoded frame dimension mismatch"};
#ifndef NDEBUG
@@ -380,7 +450,7 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const*
return ret;
}
/*
#include <SDL2/SDL.h>
struct AudioCBData {
@@ -651,4 +721,4 @@ void MveDecoder::play(unsigned branch) const
printf("Frame times: [%u, %u]\n",
minFT, maxFT);
}
*/

View File

@@ -14,25 +14,52 @@ public:
return branches_.size();
}
void play(unsigned branch) const;
private:
IffFile iff_;
using Frame = std::vector<uint8_t>;
using Audio = std::vector<uint8_t>;
using Palette = std::array<uint8_t, 768>;
struct Frame {
std::vector<uint8_t> pixels;
};
private:
using AUDIVec = std::vector<IffFile::Object const*>;
using VGAVec = std::vector<IffFile::Object const*>;
struct Shot {
Palette const& palt;
std::vector<IffFile::Object const*> AUDIs;
std::vector<IffFile::Object const*> VGAs;
AUDIVec AUDIs;
VGAVec VGAs;
};
public:
class Movie {
public:
bool hasNext() const;
std::tuple<Frame const&, Palette const&> decodeNext();
bool hasAudio() const;
Audio getNextAudio();
unsigned getWidth() const;
unsigned getHeight() const;
private:
Movie(std::vector<Shot>::const_iterator shotIt,
std::vector<Shot>::const_iterator shotEnd,
MveDecoder const& mve);
std::vector<Shot>::const_iterator shotIt_, shotEnd_;
VGAVec::const_iterator frameIt_;
AUDIVec::const_iterator audioIt_;
Frame frame_;
MveDecoder const& mve_;
friend class MveDecoder;
};
Movie open(size_t branch) const;
private:
IffFile iff_;
unsigned width_, height_;
//std::vector<Shot> shots_;
std::vector<Palette> palts_;
std::vector<std::vector<Shot> > branches_;

24
Singleton.hh Normal file
View File

@@ -0,0 +1,24 @@
#ifndef WC3RE_SINGLETON_HH__
#define WC3RE_SINGLETON_HH__
#include <memory>
#include <mutex>
template<typename T>
class Singleton {
public:
static T& getInstance() {
call_once(instance_flag, init);
return *instance;
}
private:
static std::unique_ptr<T> instance;
static std::once_flag instance_flag;
static void init() {
instance.reset(new T{});
}
};
#endif

94
game/GSMvePlay.cc Normal file
View File

@@ -0,0 +1,94 @@
#include "MveDecoder.hh"
#include "GSMvePlay.hh"
#include "render/Overlay.hh"
#include "render/Renderer.hh"
namespace game {
GSMvePlay::GSMvePlay(render::Renderer& renderer, MveDecoder::Movie& movie)
: GameState(renderer), movie_(movie),
overlay_(nullptr), delta_(0),
nextFT_(1000/15.0f), frameRGB_()
{
int scrWidth = renderer_.getWidth(), scrHeight = renderer_.getHeight();
const float mvePAR = 1.2f;
int mveWidth = movie_.getWidth(), mveHeight = movie_.getHeight();
int top = 0, left = 0;
if ((static_cast<float>(mveWidth)/(mveHeight*mvePAR)) > (static_cast<float>(scrWidth)/scrHeight)) {
// letterbox
int newScrHeight = scrWidth/(static_cast<float>(mveWidth)/(mveHeight*mvePAR));
top = (scrHeight-newScrHeight)/2;
scrHeight = newScrHeight;
} else {
// pillarbox
int newScrWidth = scrHeight*(static_cast<float>(mveWidth)/(mveHeight*mvePAR));
left = (scrWidth-newScrWidth)/2;
scrWidth = newScrWidth;
}
overlay_ = std::make_unique<render::Overlay>(renderer_, scrWidth, scrHeight, left, top,
mveWidth, mveHeight);
auto align4Width = (mveWidth%4)?(mveWidth+(4-(mveWidth%4))):mveWidth;
frameRGB_.resize(align4Width*mveHeight*3);
decode_();
overlay_->setContentRGB8(frameRGB_.data());
decode_();
}
GSMvePlay::~GSMvePlay()
{
}
void GSMvePlay::decode_()
{
if (movie_.hasNext()) {
auto dec = movie_.decodeNext();
auto& frame = std::get<0>(dec);
auto& palette = std::get<1>(dec);
size_t i = 0;
for (unsigned y = 0;y < movie_.getHeight();++y) {
for (unsigned x = 0;x < movie_.getWidth();++x) {
auto idx = frame[y*movie_.getWidth()+x];
std::copy(&palette[idx*3], &palette[idx*3+3],
&frameRGB_[i]);
i += 3;
}
if ((i%4) != 0)
i += 4-(i%4);
}
} else
frameRGB_.resize(0);
}
void GSMvePlay::draw(unsigned delta_ms)
{
const float frameTime = 1000/15.0f;
delta_ += delta_ms;
if (delta_ >= nextFT_) {
if (!frameRGB_.size()) {
renderer_.popGS();
return;
}
if (delta_-nextFT_ <= frameTime)
nextFT_ = frameTime-(delta_-nextFT_);
else
nextFT_ = frameTime;
delta_ = 0;
overlay_->setContentRGB8(frameRGB_.data());
// Decode next frame
decode_();
}
overlay_->draw();
}
}

32
game/GSMvePlay.hh Normal file
View File

@@ -0,0 +1,32 @@
#ifndef WC3RE_GAME_GSMVEPLAY_HH__
#define WC3RE_GAME_GSMVEPLAY_HH__
#include "GameState.hh"
#include "render/sdlutil.hh"
class MveDecoder;
namespace render {
class Overlay;
}
namespace game {
class GSMvePlay : public GameState {
public:
GSMvePlay(render::Renderer& renderer, MveDecoder::Movie& movie);
~GSMvePlay() override;
void draw(unsigned delta_ms) override;
private:
MveDecoder::Movie& movie_;
std::unique_ptr<render::Overlay> overlay_;
unsigned delta_;
float nextFT_;
std::vector<uint8_t> frameRGB_;
void decode_();
};
}
#endif

25
game/GameState.hh Normal file
View File

@@ -0,0 +1,25 @@
#ifndef WC3RE_GAME_GAMESTATE_HH__
#define WC3RE_GAME_GAMESTATE_HH__
namespace render {
class Renderer;
}
namespace game {
class GameState {
public:
GameState(render::Renderer& renderer)
: renderer_(renderer) {
}
virtual ~GameState() {
}
virtual void draw(unsigned delta_ms) = 0;
protected:
render::Renderer& renderer_;
};
}
#endif

View File

@@ -12,6 +12,10 @@
#include "TreFile.hh"
#include "IffFile.hh"
#include "MveDecoder.hh"
#include "render/Renderer.hh"
#include "game/GSMvePlay.hh"
using render::Renderer;
void usage(char *argv0) {
fprintf(stderr, "Usage: %s [-h] (tre-file name/crc)/iff-file\n", argv0);
@@ -88,11 +92,19 @@ int main(int argc, char *argv[]) {
mve = std::make_unique<MveDecoder>(mmap.data(), mmap.size());
if (play) {
if (branch >= 0)
mve->play(branch);
else
for(unsigned i = 0;i < mve->numBranches();++i)
mve->play(i);
Renderer renderer;
if (branch >= 0) {
auto movie = mve->open(branch);
renderer.pushGS(std::make_unique<game::GSMvePlay>(renderer, movie));
renderer.run();
} else {
for(unsigned i = 0;i < mve->numBranches();++i) {
auto movie = mve->open(i);
renderer.pushGS(std::make_unique<game::GSMvePlay>(renderer, movie));
renderer.run();
}
}
}
} catch (POSIXException &ex) {
fflush(stdout);

23
render/Drawable.hh Normal file
View File

@@ -0,0 +1,23 @@
#ifndef WC3RE_RENDER_DRAWABLE_HH__
#define WC3RE_RENDER_DRAWABLE_HH__
namespace render {
class Renderer;
class Drawable {
public:
Drawable(Renderer& renderer)
: renderer_(renderer) {
}
virtual ~Drawable() {
}
virtual void draw() = 0;
protected:
Renderer& renderer_;
};
}
#endif

View File

@@ -4,10 +4,25 @@
using namespace gl;
void TextureDeleter::operator()(gl::GLuint tex) const {
glDeleteTextures(1, &tex);
}
namespace render {
void TextureDeleter::operator() (gl::GLsizei count, gl::GLuint tex[]) const {
glDeleteTextures(count, tex);
void TextureDeleter::operator()(gl::GLuint tex) const {
glDeleteTextures(1, &tex);
}
void TextureDeleter::operator() (gl::GLsizei count, gl::GLuint tex[]) const {
glDeleteTextures(count, tex);
}
void ProgramDeleter::operator()(gl::GLuint prog) const {
glDeleteProgram(prog);
}
void ShaderDeleter::operator()(gl::GLuint shader) const {
glDeleteShader(shader);
}
void VertexArrayDeleter::operator()(gl::GLuint varr) const {
glDeleteVertexArrays(1, &varr);
}
}

View File

@@ -3,69 +3,90 @@
#include <glbinding/gl/types.h>
struct TextureDeleter {
void operator() (gl::GLuint tex) const;
void operator() (gl::GLsizei count, gl::GLuint tex[]) const;
};
namespace render {
struct TextureDeleter {
void operator() (gl::GLuint tex) const;
void operator() (gl::GLsizei count, gl::GLuint tex[]) const;
};
struct FramebufferDeleter {
void operator() (gl::GLuint tex) const;
};
struct FramebufferDeleter {
void operator() (gl::GLuint tex) const;
};
struct ShaderDeleter {
void operator() (gl::GLuint tex) const;
};
struct ShaderDeleter {
void operator() (gl::GLuint tex) const;
};
struct ProgramDeleter {
void operator() (gl::GLuint tex) const;
};
struct ProgramDeleter {
void operator() (gl::GLuint tex) const;
};
struct VertexArrayDeleter {
void operator() (gl::GLuint tex) const;
};
struct VertexArrayDeleter {
void operator() (gl::GLuint tex) const;
};
struct BufferDeleter {
void operator() (gl::GLuint tex) const;
};
struct BufferDeleter {
void operator() (gl::GLuint tex) const;
};
template<class Deleter, class Handle = gl::GLuint>
class GlResource {
public:
GlResource()
: handle_() {
}
template<class Deleter, class Handle = gl::GLuint>
class GlResource {
public:
GlResource()
: handle_() {
}
GlResource(Handle handle)
: handle_(handle) {
}
GlResource(Handle handle)
: handle_(handle) {
}
~GlResource() {
if (handle_)
Deleter()(handle_);
handle_ = Handle();
}
GlResource(GlResource const& copy) = delete;
GlResource& operator=(GlResource const& copy) = delete;
operator Handle() const {
return handle_;
}
GlResource(GlResource && move)
: handle_(move.handle_) {
move.handle_ = Handle();
}
Handle& get() {
return handle_;
}
GlResource& operator=(GlResource && move) {
if (handle_)
Deleter()(handle_);
handle_ = move.handle_;
move.handle_ = Handle();
}
~GlResource() {
if (handle_)
Deleter()(handle_);
}
Handle const& get() const {
return handle_;
}
operator Handle() const {
return handle_;
}
operator Handle&() {
return handle_;
}
Handle& get() {
return handle_;
}
Handle const& get() const {
return handle_;
}
private:
Handle handle_;
};
private:
Handle handle_;
};
using TextureResource = GlResource<TextureDeleter>;
using FramebufferResource = GlResource<FramebufferDeleter>;
using ShaderResource = GlResource<ShaderDeleter>;
using ProgramResource = GlResource<ProgramDeleter>;
using VertexArrayResource = GlResource<VertexArrayDeleter>;
using BufferResource = GlResource<BufferDeleter>;
using TextureResource = GlResource<TextureDeleter>;
using FramebufferResource = GlResource<FramebufferDeleter>;
using ShaderResource = GlResource<ShaderDeleter>;
using ProgramResource = GlResource<ProgramDeleter>;
using VertexArrayResource = GlResource<VertexArrayDeleter>;
using BufferResource = GlResource<BufferDeleter>;
}
#endif

97
render/Overlay.cc Normal file
View File

@@ -0,0 +1,97 @@
#include <glbinding/gl/gl.h>
#include <SDL2/SDL.h>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Overlay.hh"
#include "renderutil.hh"
#include "VBOManager.hh"
#include "ProgramProvider.hh"
#include "Renderer.hh"
using namespace gl;
namespace render {
namespace {
struct VertexAttribs {
int16_t vertex[2];
uint16_t texCoords[2];
} __attribute__((__packed__));
}
Overlay::Overlay(Renderer& renderer, int width, int height, int left, int top, int intWidth, int intHeight)
: Drawable(renderer), texture_(create2DTexture(intWidth, intHeight, false, 1)),
vbo_(VBOManager::getInstance().alloc(sizeof(VertexAttribs)*6)),
width_(width), height_(height), top_(top), left_(left), intWidth_(intWidth), intHeight_(intHeight)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<int>(GL_LINEAR));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, static_cast<int>(GL_CLAMP_TO_EDGE));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, static_cast<int>(GL_CLAMP_TO_EDGE));
program_ = ProgramProvider::getInstance().getProgram("overlay", "overlay");
glGenVertexArrays(1, &vertexArray_.get());
short int t = top_, l = left_, b = height+top_, r = width+left_;
std::vector<VertexAttribs> vertexAttribs{
{{l, t}, {0, 0}},
{{r, b}, {65535u, 65535u}},
{{r, t}, {65535u, 0}},
{{l, t}, {0, 0}},
{{l, b}, {0, 65535u}},
{{r, b}, {65535u, 65535u}}};
glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId());
glBindVertexArray(vertexArray_);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_SHORT, GL_FALSE, sizeof(VertexAttribs),
vbo_.getOfs(offsetof(VertexAttribs, vertex)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(VertexAttribs),
vbo_.getOfs(offsetof(VertexAttribs, texCoords)));
glBufferSubData(GL_ARRAY_BUFFER, vbo_.getBase(),
sizeof(VertexAttribs)*6,
vertexAttribs.data());
ovlProj_ = glm::ortho(0.0f, static_cast<float>(renderer_.getWidth()),
static_cast<float>(renderer_.getHeight()), 0.0f);
}
void Overlay::draw()
{
glDisable(GL_DEPTH_TEST);
glUseProgram(program_);
glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(ovlProj_));
glUniform1i(1, 0);
glBindTexture(GL_TEXTURE_2D, texture_);
glBindVertexArray(vertexArray_);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
void Overlay::setContent(SDL_Surface *content)
{
if (!content ||
(content->h != intHeight_) ||
(content->w != intWidth_))
throw Exception{"null or mismatched surface"};
glBindTexture(GL_TEXTURE_2D, texture_);
if (content->format->format != SDL_PIXELFORMAT_RGB24) {
SDLSurfaceUPtr tmpSurf(SDL_ConvertSurfaceFormat(content, SDL_PIXELFORMAT_RGB24, 0));
if (!tmpSurf)
throw SDLException{};
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tmpSurf->w, tmpSurf->h, GL_RGB, GL_UNSIGNED_BYTE, tmpSurf->pixels);
} else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, content->w, content->h, GL_RGB, GL_UNSIGNED_BYTE, content->pixels);
}
void Overlay::setContentRGB8(void *data)
{
glBindTexture(GL_TEXTURE_2D, texture_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, intWidth_, intHeight_, GL_RGB, GL_UNSIGNED_BYTE, data);
}
}

33
render/Overlay.hh Normal file
View File

@@ -0,0 +1,33 @@
#ifndef WC3RE_RENDER_OVERLAY_HH__
#define WC3RE_RENDER_OVERLAY_HH__
#include <SDL2/SDL.h>
#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include "Drawable.hh"
#include "GlResource.hh"
#include "VBOManager.hh"
namespace render {
/* A two-dimensional surface which can be displayed */
class Overlay : public Drawable {
public:
Overlay(Renderer& renderer, int width, int height, int left, int top, int intWidth, int intHeight);
void draw() override;
void setContent(SDL_Surface *content);
void setContentRGB8(void *data);
private:
TextureResource texture_;
VertexArrayResource vertexArray_;
gl::GLuint program_;
VBOManager::VBOAlloc vbo_;
int width_, height_, top_, left_, intWidth_, intHeight_;
glm::mat4 ovlProj_;
};
}
#endif

99
render/ProgramProvider.cc Normal file
View File

@@ -0,0 +1,99 @@
#include <mutex>
#include <glbinding/gl/gl.h>
#include "ProgramProvider.hh"
#include "exceptions.hh"
#include "util.hh"
using namespace gl;
template<>
std::unique_ptr<render::ProgramProvider> Singleton<render::ProgramProvider>::instance{nullptr};
template<>
std::once_flag Singleton<render::ProgramProvider>::instance_flag{};
namespace render {
namespace {
class ShaderException : public Exception {
public:
ShaderException(std::string const& msg)
: Exception(msg)
{
}
std::string toString() const override {
return "ShaderException: " + msg_;
}
};
}
ProgramProvider::ProgramProvider()
{
}
void shaderCompile_(std::string const& shaderName, ShaderResource& shader)
{
std::string prog = fileToString("shaders/" + shaderName);
const char* const arr[] = {prog.c_str()};
glShaderSource(shader, 1, arr, NULL);
glCompileShader(shader);
int state;
glGetShaderiv(shader, GL_COMPILE_STATUS, &state);
if (state == 0) {
int logLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
char *log = new char[logLength];
glGetShaderInfoLog(shader, logLength, NULL, log);
std::string msg(log);
delete[] log;
throw ShaderException(msg);
}
}
GLuint ProgramProvider::getProgram(std::string const& vertexShader, std::string const& fragShader)
{
auto key = make_tuple(vertexShader, fragShader);
auto it = programCache_.find(key);
if (it != programCache_.end())
return it->second.get();
ShaderResource vs(glCreateShader(GL_VERTEX_SHADER)),
fs(glCreateShader(GL_FRAGMENT_SHADER));
shaderCompile_(vertexShader + ".vs", vs);
shaderCompile_(fragShader + ".fs", fs);
ProgramResource prog(glCreateProgram());
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
int state;
glGetProgramiv(prog, GL_LINK_STATUS, &state);
if (state == 0) {
int logLength;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
char *log = new char[logLength];
glGetProgramInfoLog(prog, logLength, NULL, log);
std::string msg(log);
delete[] log;
throw ShaderException(msg);
}
glDetachShader(prog, vs);
glDetachShader(prog, fs);
std::tie(it, std::ignore) = programCache_.insert(make_pair(key, std::move(prog)));
return it->second.get();
}
}

41
render/ProgramProvider.hh Normal file
View File

@@ -0,0 +1,41 @@
#ifndef WC3RE_RENDER_PROGRAMPROVIDER_HH__
#define WC3RE_RENDER_PROGRAMPROVIDER_HH__
#include <unordered_map>
#include "Singleton.hh"
#include "GlResource.hh"
namespace render {
using ProgramProviderKeyType = std::tuple<std::string, std::string>;
}
namespace std
{
template<>
struct hash<render::ProgramProviderKeyType> {
typedef render::ProgramProviderKeyType argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const& s) const {
const result_type h1{std::hash<std::string>()(std::get<0>(s))};
const result_type h2{std::hash<std::string>()(std::get<1>(s))};
return h1 ^ h2;
}
};
}
namespace render {
class ProgramProvider : public Singleton<ProgramProvider> {
private:
ProgramProvider();
friend class Singleton<ProgramProvider>;
public:
gl::GLuint getProgram(std::string const& vertexShader, std::string const& fragShader);
private:
std::unordered_map<ProgramProviderKeyType, ProgramResource> programCache_;
};
}
#endif

View File

@@ -1,12 +1,152 @@
#include <glbinding/gl/gl.h>
#include <glbinding/Binding.h>
#include <glbinding/callbacks.h>
#include "Renderer.hh"
#include "GlResource.hh"
#include "game/GameState.hh"
#include "exceptions.hh"
using namespace gl;
Renderer::Renderer()
{
TextureResource testTex;
glGenTextures(1, &testTex.get());
namespace render {
class GLException : public Exception {
public:
GLException(GLenum err) : Exception(), err_(err) {
}
GLenum getErr() const {return err_;}
std::string errToString() const {
std::string ret;
if (err_ == GL_INVALID_ENUM)
ret += "GL_INVALID_ENUM ";
if (err_ == GL_INVALID_VALUE)
ret += "GL_INVALID_VALUE ";
if (err_ == GL_INVALID_OPERATION)
ret += "GL_INVALID_OPERATION ";
if (err_ == GL_INVALID_FRAMEBUFFER_OPERATION)
ret += "GL_INVALID_FRAMEBUFFER_OPERATION ";
if (err_ == GL_OUT_OF_MEMORY)
ret += "GL_OUT_OF_MEMORY ";
if (err_ == GL_STACK_UNDERFLOW)
ret += "GL_STACK_UNDERFLOW ";
if (err_ == GL_STACK_OVERFLOW)
ret += "GL_STACK_OVERFLOW ";
return ret;
}
std::string toString() const override {
return "GLException: " + errToString() + "(" + std::to_string(static_cast<int>(err_)) + ")";
}
private:
GLenum err_;
};
void glAfterCallback(glbinding::FunctionCall const& fc)
{
glbinding::setCallbackMask(glbinding::CallbackMask::None);
GLenum error = glGetError();
glbinding::setCallbackMask(glbinding::CallbackMask::After);
if (error != GL_NO_ERROR)
throw GLException(error);
}
Renderer::Renderer()
: sdlInit_(), window_(), context_()
{
glbinding::Binding::initialize();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
window_.reset(SDL_CreateWindow("WC3 RE",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
0, 0,
SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_OPENGL));
if (!window_)
throw SDLException{};
context_ = SDLGLContext(window_.get());
SDL_GetWindowSize(window_.get(), &width_, &height_);
glbinding::setCallbackMask(glbinding::CallbackMask::After);
glbinding::setAfterCallback(glAfterCallback);
#ifndef NDEBUG
{
int depth, stencil, aa, major, minor;
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depth);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencil);
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &aa);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
printf("Depth: %d, Stencil: %d, AA: %d, GL %d.%d\n",
depth, stencil, aa, major, minor);
}
#endif
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glViewport(0, 0, width_, height_);
}
void Renderer::run()
{
bool close = false;
auto last = SDL_GetTicks();
while (!close && gamestates_.size()) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch(event.type) {
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_q)
close = true;
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_CLOSE)
close = true;
break;
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auto now = SDL_GetTicks();
gamestates_.back()->draw(now-last);
SDL_GL_SwapWindow(window_.get());
last = now;
}
}
void Renderer::pushGS(std::unique_ptr<game::GameState> gs)
{
gamestates_.emplace_back(std::move(gs));
}
std::unique_ptr<game::GameState> Renderer::popGS()
{
auto ret = std::move(gamestates_.back());
gamestates_.pop_back();
return ret;
}
int Renderer::getWidth() const
{
return width_;
}
int Renderer::getHeight() const
{
return height_;
}
}

View File

@@ -1,14 +1,32 @@
#ifndef WC3RE_RENDER_RENDERER_HH__
#define WC3RE_RENDER_RENDERER_HH__
#include <SDL2/SDL.h>
#include "sdlutil.hh"
class Renderer {
public:
Renderer();
namespace game {
class GameState;
}
private:
SDL_Window *win;
};
namespace render {
class Renderer {
public:
Renderer();
void run();
int getWidth() const;
int getHeight() const;
void pushGS(std::unique_ptr<game::GameState> gs);
std::unique_ptr<game::GameState> popGS();
private:
SDLInit sdlInit_;
SDLWindowUPtr window_;
SDLGLContext context_;
std::vector<std::unique_ptr<game::GameState> > gamestates_;
int width_, height_;
};
}
#endif

154
render/VBOManager.cc Normal file
View File

@@ -0,0 +1,154 @@
#include <glbinding/gl/gl.h>
#include <cassert>
#include <mutex>
#include "VBOManager.hh"
using namespace gl;
template<>
std::unique_ptr<render::VBOManager> Singleton<render::VBOManager>::instance{nullptr};
template<>
std::once_flag Singleton<render::VBOManager>::instance_flag{};
namespace render {
VBOManager::VBOManager()
{
}
VBOManager::~VBOManager()
{
}
VBOManager::VBOAlloc::~VBOAlloc()
{
if(_vbo)
_vbo->free(*this);
_vbo = nullptr;
}
VBOManager::VBOAlloc::VBOAlloc(VBOAlloc&& move)
: _vbo(move._vbo), _ofs(move._ofs), _size(move._size)
{
move._vbo = nullptr;
}
VBOManager::VBOAlloc& VBOManager::VBOAlloc::operator=(VBOAlloc&& move)
{
_vbo = move._vbo;
_ofs = move._ofs;
_size = move._size;
move._vbo = nullptr;
return *this;
}
VBOManager::VBOAlloc::VBOAlloc()
: _vbo(nullptr), _ofs(0), _size(0)
{
}
VBOManager::VBOAlloc::VBOAlloc(VBO &vbo, size_t ofs, size_t size)
: _vbo(&vbo), _ofs(ofs), _size(size)
{
}
VBOManager::VBOAlloc VBOManager::alloc(size_t size, GLenum type)
{
for(auto& vbo : _vbos[type]) {
try {
return vbo.alloc(size);
} catch (AllocFailed &ex) {
continue;
}
}
_vbos[type].emplace_back((size>default_size)?size:default_size, type);
return _vbos[type].back().alloc(size);
}
VBOManager::VBO::VBO(size_t size, GLenum type)
: _bufID(0)
{
glGenBuffers(1, &_bufID);
glBindBuffer(GL_ARRAY_BUFFER, _bufID);
glBufferData(GL_ARRAY_BUFFER, size, NULL, type);
_allocs.emplace_back(_Entry{size, false});
}
VBOManager::VBO::VBO(VBO&& move)
: _bufID(move._bufID), _allocs(std::move(move._allocs))
{
move._bufID = 0;
}
VBOManager::VBO& VBOManager::VBO::operator=(VBO&& move)
{
for (auto ent : _allocs) {
assert(!ent.used);
}
if (_bufID)
glDeleteBuffers(1, &_bufID);
_bufID = move._bufID;
move._bufID = 0;
_allocs = std::move(move._allocs);
return *this;
}
VBOManager::VBO::~VBO()
{
for (auto ent : _allocs) {
assert(!ent.used);
}
if (_bufID)
glDeleteBuffers(1, &_bufID);
_bufID = 0;
}
VBOManager::VBOAlloc VBOManager::VBO::alloc(size_t size)
{
if (size%alignment != 0)
size += (alignment - (size%alignment));
size_t pos = 0;
for (auto it = _allocs.begin();it != _allocs.end();++it) {
if (!it->used && (it->size >= size)) {
size_t leftover = it->size - size;
it->used = true;
it->size = size;
if (leftover > 0)
_allocs.insert(++it, _Entry{leftover, false});
printf("DEBUG: VBO: Allocated %lu @ %lu in %u\n", size, pos, _bufID);
return VBOAlloc(*this, pos, size);
}
pos += it->size;
}
throw AllocFailed();
}
void VBOManager::VBO::free(VBOAlloc& alloc)
{
size_t pos = 0;
for (auto it = _allocs.begin();it != _allocs.end();++it) {
if (pos == alloc._ofs) {
assert(it->size == alloc._size);
printf("DEBUG: VBO: Freed %lu @ %lu in %u\n", alloc._size, pos, _bufID);
it->used = false;
if ((std::next(it) != _allocs.end()) &&
!std::next(it)->used) {
it->size += std::next(it)->size;
_allocs.erase(std::next(it));
}
if ((it != _allocs.begin()) &&
!std::prev(it)->used) {
it->size += std::prev(it)->size;
_allocs.erase(std::prev(it));
}
return;
}
pos += it->size;
}
}
}

124
render/VBOManager.hh Normal file
View File

@@ -0,0 +1,124 @@
#ifndef WC3RE_RENDER_VBOMANAGER_HH__
#define WC3RE_RENDER_VBOMANAGER_HH__
#include <map>
#include <list>
#include <memory>
#include <cassert>
#include <vector>
#include <glbinding/gl/types.h>
#include <glbinding/gl/enum.h>
#include "Singleton.hh"
namespace render {
class VBOManager : public Singleton<VBOManager> {
public:
~VBOManager();
VBOManager(VBOManager const& copy) = delete;
VBOManager& operator=(VBOManager const& copy) = delete;
private:
VBOManager();
friend class Singleton<VBOManager>;
class VBO;
static const size_t default_size = 1048576;
static const size_t alignment = 8;
static const gl::GLenum default_type = gl::GL_STATIC_DRAW;
class AllocFailed {};
public:
class VBOAlloc {
public:
~VBOAlloc();
VBOAlloc(VBOAlloc&& move);
VBOAlloc(VBOAlloc const& copy) = delete;
VBOAlloc& operator=(VBOAlloc const& copy) = delete;
VBOAlloc& operator=(VBOAlloc&& move);
VBOAlloc();
size_t getBase() const {
assert(_vbo);
return _ofs;
}
void * getOfs(size_t ofs) const {
assert(_vbo);
assert(ofs < _size);
return reinterpret_cast<void*>(_ofs + ofs);
}
size_t getSize() const {
assert(_vbo);
return _size;
}
gl::GLuint getVBOId() const {
assert(_vbo);
return _vbo->getID();
}
explicit operator bool() const noexcept {
return (_vbo != nullptr);
}
private:
VBOAlloc(VBO &vbo, size_t ofs, size_t size);
friend class VBOManager;
friend class VBO;
VBO *_vbo;
size_t _ofs, _size;
};
VBOAlloc alloc(size_t size, gl::GLenum type = default_type);
private:
class VBO {
public:
VBO(size_t size = default_size, gl::GLenum type = default_type);
VBO(VBO const& copy) = delete;
VBO& operator=(VBO const& copy) = delete;
VBO(VBO&& move);
VBO& operator=(VBO&& move);
~VBO();
VBOAlloc alloc(size_t size);
void free(VBOAlloc& alloc);
gl::GLuint getID() const {
return _bufID;
}
private:
gl::GLuint _bufID;
struct _Entry {
size_t size;
bool used;
};
std::list<_Entry> _allocs;
};
std::map<gl::GLenum, std::vector<VBO> > _vbos;
};
}
#endif

42
render/renderutil.cc Normal file
View File

@@ -0,0 +1,42 @@
#include <glbinding/gl/gl.h>
#include <SDL2/SDL.h>
#include "renderutil.hh"
using namespace gl;
namespace render {
namespace {
unsigned ilog2(unsigned in)
{
unsigned ret = 0u;
while (in >>= 1) ++ret;
return ret;
}
}
TextureResource create2DTexture(unsigned width, unsigned height, bool alpha, unsigned levels)
{
TextureResource tex;
glGenTextures(1, &tex.get());
glBindTexture(GL_TEXTURE_2D, tex);
unsigned logWidth = ilog2(width), logHeight = ilog2(height);
if (levels == 0)
levels = std::max(logWidth,logHeight)+1u;
if(SDL_GL_ExtensionSupported("GL_ARB_texture_storage"))
glTexStorage2D(GL_TEXTURE_2D, levels, alpha?GL_RGBA8:GL_RGB8, width, height);
else {
std::printf("Warning: extension GL_ARB_texture_storage not supported!\n");
for (unsigned i = 0u; i < levels; ++i) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
glTexImage2D(GL_TEXTURE_2D, i, static_cast<const int>(alpha?GL_RGBA8:GL_RGB8), width, height, 0,
alpha?GL_RGBA8:GL_RGB8, GL_UNSIGNED_BYTE, NULL);
width = std::max(1u, (width / 2u));
height = std::max(1u, (height / 2u));
}
}
return tex;
}
}

10
render/renderutil.hh Normal file
View File

@@ -0,0 +1,10 @@
#ifndef WC3RE_RENDER_RENDERUTIL_HH__
#define WC3RE_RENDER_RENDERUTIL_HH__
#include "GlResource.hh"
namespace render {
TextureResource create2DTexture(unsigned width, unsigned height, bool alpha, unsigned levels = 0);
}
#endif

141
render/sdlutil.hh Normal file
View File

@@ -0,0 +1,141 @@
#ifndef WC3RE_RENDER_SDLUTIL_HH__
#define WC3RE_RENDER_SDLUTIL_HH__
#include <memory>
#include <cstdint>
#include <SDL2/SDL.h>
#include "exceptions.hh"
namespace render {
class SDLException : public Exception {
public:
SDLException() : Exception(SDL_GetError()) {
}
std::string toString() const override {
return "SDLException: " + msg_;
}
};
// Some helpers to C++11-ify SDL
struct SDLSurfaceDeleter {
void operator()(SDL_Surface *ptr) const {
SDL_FreeSurface(ptr);
}
};
using SDLSurfaceUPtr = std::unique_ptr<SDL_Surface, SDLSurfaceDeleter>;
struct SDLWindowDeleter {
void operator()(SDL_Window *ptr) const {
SDL_DestroyWindow(ptr);
}
};
using SDLWindowUPtr = std::unique_ptr<SDL_Window, SDLWindowDeleter>;
class SDLSurfaceScopedLock {
public:
SDLSurfaceScopedLock(SDL_Surface *surf) : surf_(surf) {
if (SDL_MUSTLOCK(surf_))
if (SDL_LockSurface(surf_) != 0)
throw SDLException{};
}
SDLSurfaceScopedLock(SDLSurfaceUPtr& surf) : surf_(surf.get()) {
if (SDL_MUSTLOCK(surf_))
if (SDL_LockSurface(surf_) != 0)
throw SDLException{};
}
SDLSurfaceScopedLock(SDLSurfaceScopedLock const& copy) = delete;
SDLSurfaceScopedLock& operator=(SDLSurfaceScopedLock const& copy) = delete;
~SDLSurfaceScopedLock() {
if (surf_ && SDL_MUSTLOCK(surf_))
SDL_UnlockSurface(surf_);
}
void unlock() {
if (surf_ && SDL_MUSTLOCK(surf_))
SDL_UnlockSurface(surf_);
surf_ = nullptr;
}
private:
SDL_Surface *surf_;
};
static bool operator==(SDL_Color const& a, SDL_Color const& b) {
return ((a.r == b.r) && (a.g == b.g) && (a.b == b.b) && (a.a == b.a));
}
static bool operator!=(SDL_Color const& a, SDL_Color const& b) {
return ((a.r != b.r) || (a.g != b.g) || (a.b != b.b) || (a.a != b.a));
}
// RAII wrapper for SDL_Init
class SDLInit {
public:
SDLInit(uint32_t flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO) {
if (SDL_Init(flags) < 0)
throw SDLException{};
}
SDLInit(SDLInit const& copy) = delete;
SDLInit& operator=(SDLInit const& copy) = delete;
~SDLInit() {
SDL_Quit();
}
};
// RAII wrapper for SDL_GLContext
class SDLGLContext {
public:
SDLGLContext(SDL_Window *win)
: context_(SDL_GL_CreateContext(win)) {
if (!context_)
throw SDLException{};
}
SDLGLContext()
: context_(nullptr) {
}
SDLGLContext(SDLGLContext const& copy) = delete;
SDLGLContext& operator=(SDLGLContext const& copy) = delete;
SDLGLContext(SDLGLContext && move):
context_(move.context_) {
move.context_ = nullptr;
}
SDLGLContext& operator=(SDLGLContext && move) {
if (context_)
SDL_GL_DeleteContext(context_);
context_ = move.context_;
move.context_ = nullptr;
return *this;
}
~SDLGLContext() {
if (context_)
SDL_GL_DeleteContext(context_);
}
operator SDL_GLContext() const {
return context_;
}
private:
SDL_GLContext context_;
};
}
#endif

12
shaders/overlay.fs Normal file
View File

@@ -0,0 +1,12 @@
#version 330 core
#extension GL_ARB_explicit_uniform_location : enable
layout(location = 1) uniform sampler2D texBase;
in vec2 fragTC;
out vec4 color;
void main(void) {
color = texture(texBase, fragTC);
}

16
shaders/overlay.vs Normal file
View File

@@ -0,0 +1,16 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
#extension GL_ARB_explicit_uniform_location : enable
layout(location = 0) uniform mat4 projection_matrix;
layout(location = 0) in vec2 vertex;
layout(location = 1) in vec2 vertexTC;
out vec2 fragTC;
void main(void) {
vec4 pos = projection_matrix * vec4(vertex, 0.0, 1.0);
gl_Position = pos;
fragTC = vertexTC;
}

23
util.cc
View File

@@ -158,3 +158,26 @@ int sextend(unsigned b, unsigned msb)
return b&((1<<msb)-1);
}
}
std::string fileToString(std::string const& name) {
std::FILE *file = std::fopen(name.c_str(), "r");
if (!file) {
throw POSIXException(errno);
}
std::string ret;
char buf[512];
std::size_t p;
while((p = std::fread(buf, 1, 511, file)) > 0) {
buf[p] = '\0';
ret.append(buf);
}
if (!std::feof(file)) {
std::fclose(file);
throw POSIXException(errno);
}
std::fclose(file);
return ret;
}

View File

@@ -42,4 +42,6 @@ uint32_t readU32LE(char const* data);
// Sign-extend b starting at msb
int sextend(unsigned b, unsigned msb);
// Load simple resource from file
std::string fileToString(std::string const& name);
#endif