get screen pixels uses gl or sdl

This commit is contained in:
Frank DeMarco 2019-06-16 03:05:02 -04:00
parent 7e51a04b71
commit 30e6c970d3
16 changed files with 463 additions and 126 deletions

View File

@ -86,45 +86,51 @@ GLuint get_gl_texture_from_surface(SDL_Surface *surface, GLint mipmap_filter)
SDL_Surface* get_framerate_indicator_surface(int frame_count) SDL_Surface* get_framerate_indicator_surface(int frame_count)
{ {
TTF_Font *font = TTF_OpenFont("resource/SourceCodePro-Regular.otf", 14); TTF_Font* font = TTF_OpenFont("resource/SourceCodePro-Regular.otf", 14);
SDL_Surface *shaded = TTF_RenderText_Shaded( std::string padded = sfw::pad(frame_count, 2);
font, std::to_string(frame_count).c_str(), {0, 0, 0}, {255, 255, 255}); SDL_Surface* shaded = TTF_RenderText_Shaded(
SDL_Surface *converted = SDL_ConvertSurfaceFormat(shaded, SDL_PIXELFORMAT_ARGB8888, 0); font, padded.c_str(), {0, 0, 0}, {255, 255, 255});
SDL_Surface *flipped = zoomSurface(converted, 1, -1, SMOOTHING_OFF); SDL_Surface* converted = SDL_ConvertSurfaceFormat(
shaded, SDL_PIXELFORMAT_ARGB8888, 0);
SDL_Surface* flipped = zoomSurface(converted, 1, -1, SMOOTHING_OFF);
SDL_FreeSurface(shaded);
SDL_FreeSurface(converted);
if (not flipped) if (not flipped)
{ {
fprintf(stderr, "Could not create text %s\n", SDL_GetError()); fprintf(stderr, "Could not create text %s\n", SDL_GetError());
} }
TTF_CloseFont(font);
return flipped; return flipped;
} }
void set_framerate_indicator(int frame_count, GLuint id) void set_framerate_indicator(int frame_count, GLuint id)
{ {
printf("%i %i", frame_count, id); SDL_Surface* message = get_framerate_indicator_surface(frame_count);
SDL_Surface *message = get_framerate_indicator_surface(frame_count);
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, message->w, message->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, message->w, message->h, GL_BGRA,
message->pixels); GL_UNSIGNED_BYTE, message->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
SDL_FreeSurface(message);
} }
Mushroom::Mushroom(Node *parent) : Sprite(parent, "resource/shrooms") Mushroom::Mushroom(Node *parent) : Sprite(parent, "resource/shrooms")
{ {
set_frame_length(500);
std::cout << "mushroom constructor " << this << std::endl; std::cout << "mushroom constructor " << this << std::endl;
} }
void Mushroom::update() void Mushroom::update()
{ {
Game *game = get_root();
move(direction); move(direction);
int x = location.get_x(); int x = location.get_x();
if (x > game->sw or x < 0) glm::ivec2 resolution = get_display().get_window_size();
if (x > resolution.x or x < 0)
{ {
direction = -direction; direction = -direction;
if (x > game->sw) if (x > resolution.x)
{ {
move(game->sw - x); move(resolution.x - x);
} }
else else
{ {
@ -140,11 +146,6 @@ Demo::Demo()
Mix_PlayMusic(music, -1); Mix_PlayMusic(music, -1);
load_gl_context(); load_gl_context();
delegate.subscribe(&Demo::respond, this); delegate.subscribe(&Demo::respond, this);
// audio_file.open("audio.raw", std::ios::binary);
// SDL_AudioSpec spec;
// audio_device_id = SDL_OpenAudioDevice(NULL, SDL_TRUE, &spec, &spec, 0);
// SDL_Log("opened audio device %i", audio_device_id);
// SDL_PauseAudioDevice(audio_device_id, SDL_FALSE);
} }
void Demo::load_sdl_context() void Demo::load_sdl_context()
@ -168,7 +169,7 @@ void Demo::load_gl_context()
v6-v1-v0 v6-v1-v0
v1-v6-v7 (left) v1-v6-v7 (left)
v7-v2-v1 v7-v2-v1
v7-v4-v3 (bottom) v7-v4-v3 (bottom)
v3-v2-v7 v3-v2-v7
v4-v7-v6 (back) v4-v7-v6 (back)
v6-v5-v4 v6-v5-v4
@ -212,12 +213,14 @@ void Demo::load_gl_context()
vertices.insert(vertices.end(), background_vertices.begin(), background_vertices.end()); vertices.insert(vertices.end(), background_vertices.begin(), background_vertices.end());
vertices.insert(vertices.end(), framerate_indicator_vertices.begin(), vertices.insert(vertices.end(), framerate_indicator_vertices.begin(),
framerate_indicator_vertices.end()); framerate_indicator_vertices.end());
projection = glm::perspective(glm::radians(45.0f), glm::ivec2 resolution = get_display().get_window_size();
(float) sw / (float) sh, 0.1f, 100.0f); projection = glm::perspective(
glm::radians(45.0f), resolution.x / (float) resolution.y, 0.1f,
100.0f);
view = glm::lookAt( view = glm::lookAt(
glm::vec3(4, 3, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); glm::vec3(4, 3, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
SDL_Surface *surface = rotateSurface90Degrees(IMG_Load("resource/tile.png"), 2); SDL_Surface *surface = rotateSurface90Degrees(IMG_Load("resource/tile.png"), 2);
printf("tile.png bytes per pixel %i\n", surface->format->BytesPerPixel); SDL_Log("tile.png bytes per pixel %i", surface->format->BytesPerPixel);
space_texture_id = get_gl_texture_from_surface(surface, GL_LINEAR); space_texture_id = get_gl_texture_from_surface(surface, GL_LINEAR);
SDL_FreeSurface(surface); SDL_FreeSurface(surface);
std::array<glm::vec2, 6> framerate_indicator_uv = { std::array<glm::vec2, 6> framerate_indicator_uv = {
@ -292,14 +295,15 @@ void Demo::load_gl_context()
glBindAttribLocation(flat_program, 3, "pos"); glBindAttribLocation(flat_program, 3, "pos");
link_shader(flat_program); link_shader(flat_program);
mvp_id = glGetUniformLocation(world_program, "MVP"); mvp_id = glGetUniformLocation(world_program, "MVP");
GLuint sampler_uniform_id = glGetUniformLocation(world_program, "myTextureSampler"); // GLuint sampler_uniform_id = glGetUniformLocation(world_program, "myTextureSampler");
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, space_texture_id); glBindTexture(GL_TEXTURE_2D, space_texture_id);
glUniform1i(sampler_uniform_id, 0); // glUniform1i(sampler_uniform_id, 0);
glDepthFunc(GL_LESS); glDepthFunc(GL_LESS);
frame_count_timestamp = SDL_GetTicks(); frame_count_timestamp = SDL_GetTicks();
framerate_texture_id = get_gl_texture_from_surface( SDL_Surface* fps_surface = get_framerate_indicator_surface(frame_count);
get_framerate_indicator_surface(frame_count), GL_LINEAR); framerate_texture_id = get_gl_texture_from_surface(fps_surface, GL_LINEAR);
SDL_FreeSurface(fps_surface);
} }
void Demo::respond(SDL_Event& event) void Demo::respond(SDL_Event& event)
@ -320,6 +324,17 @@ void Demo::respond(SDL_Event& event)
Mix_Chunk* music = Mix_LoadWAV("resource/Ag.ogg"); Mix_Chunk* music = Mix_LoadWAV("resource/Ag.ogg");
Mix_PlayChannel(-1, music, 0); Mix_PlayChannel(-1, music, 0);
} }
else if (delegate.compare(event, "fullscreen"))
{
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN)
{
SDL_SetWindowFullscreen(window, 0);
}
else
{
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
}
}
} }
void Demo::update() void Demo::update()
@ -328,18 +343,7 @@ void Demo::update()
// { // {
// if (event.type == SDL_KEYDOWN) // if (event.type == SDL_KEYDOWN)
// { // {
// if (event.key.keysym.sym == SDLK_F11) // if (SDL_GetModState() & KMOD_CTRL)
// {
// if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN)
// {
// SDL_SetWindowFullscreen(window, 0);
// }
// else
// {
// SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
// }
// }
// else if (SDL_GetModState() & KMOD_CTRL)
// { // {
// if (event.key.keysym.sym == SDLK_f) // if (event.key.keysym.sym == SDLK_f)
// { // {

View File

@ -37,6 +37,7 @@
#include "Sprite.hpp" #include "Sprite.hpp"
#include "Input.hpp" #include "Input.hpp"
#include "Delegate.hpp" #include "Delegate.hpp"
#include "extension.hpp"
struct Mushroom : Sprite struct Mushroom : Sprite
{ {
@ -60,11 +61,8 @@ struct Demo : Game
GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program, GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program,
world_program, fake_texture_id; world_program, fake_texture_id;
glm::mat4 projection, view, model = glm::mat4(1.0f), mvp; glm::mat4 projection, view, model = glm::mat4(1.0f), mvp;
int abc, def, ghi, jkl, mno, pqr, stu, vwx, yz;
Mushroom mushroom = Mushroom(this); Mushroom mushroom = Mushroom(this);
Sprite grass = Sprite(this, "resource/Field.png"); Sprite grass = Sprite(this, "resource/Field.png");
// SDL_AudioDeviceID audio_device_id;
// std::ofstream audio_file;
Demo(); Demo();
void load_sdl_context(); void load_sdl_context();

View File

@ -36,13 +36,16 @@ $(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h
$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h $(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h
$(CC_LINUX) $(CFLAGS) $< -o $@ $(CC_LINUX) $(CFLAGS) $< -o $@
$(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp Node.*pp) $(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp Node.*pp Animation.*pp)
$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp \ $(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp \
Recorder.*pp Node.*pp Input.*pp) Recorder.*pp Node.*pp Input.*pp)
$(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp Delegate.*pp) $(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp Delegate.*pp)
$(SFW_SRC_DIR)Animation.o: $(addprefix $(SFW_SRC_DIR),Timer.*pp) $(SFW_SRC_DIR)Animation.o: $(addprefix $(SFW_SRC_DIR),Timer.*pp)
$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp Node.*pp Delegate.*pp) $(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp Node.*pp Delegate.*pp Animation.*pp)
$(SFW_SRC_DIR)Input.o: $(addprefix $(SFW_SRC_DIR),Delegate.*pp) $(SFW_SRC_DIR)Input.o: $(addprefix $(SFW_SRC_DIR),Delegate.*pp Node.*pp)
$(SFW_SRC_DIR)Configuration.o: $(addprefix $(SFW_SRC_DIR),Node.*pp)
$(SFW_SRC_DIR)Delegate.o: $(addprefix $(SFW_SRC_DIR),Node.*pp)
$(SFW_SRC_DIR)Display.o: $(addprefix $(SFW_SRC_DIR),Node.*pp)
$(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp) $(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp)
$(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@

View File

@ -1,7 +1,8 @@
{ {
"display": "display":
{ {
"dimensions": [640, 480] "dimensions": [864, 486],
"fps": 60
}, },
"path": "path":
{ {
@ -14,12 +15,12 @@
"keys": "keys":
{ {
"context": " ", "context": " ",
"print-video-memory": "?", "print-video-memory-size": "v",
"play-sound": "s" "play-sound": ""
}, },
"recording": "recording":
{ {
"write-mp4": true, "write-mp4": true,
"video-frame-length": 16.667 "video-frame-length": 33.334
} }
} }

View File

@ -22,14 +22,16 @@ void Configuration::set_defaults()
{"down", "down"}, {"down", "down"},
{"left", "left"}, {"left", "left"},
{"pause", "enter"}, {"pause", "enter"},
{"fullscreen", {"ALT", "enter"}} {"fullscreen", {"ALT", "enter"}},
{"show-fps", {"CTRL", "f"}}
}; };
sys_config["path"] = { sys_config["path"] = {
{"screenshots", "."}, {"screenshots", "."},
{"video", "."} {"video", "."}
}; };
sys_config["display"] = { sys_config["display"] = {
{"dimensions", {640, 480}} {"dimensions", {640, 480}},
{"fps", 60}
}; };
sys_config["recording"] = { sys_config["recording"] = {
{"screenshot-prefix", "screenshot-"}, {"screenshot-prefix", "screenshot-"},
@ -43,6 +45,12 @@ void Configuration::set_defaults()
{"max-video-memory", 1000}, {"max-video-memory", 1000},
{"mp4-pixel-format", "yuv444p"} {"mp4-pixel-format", "yuv444p"}
}; };
sys_config["fps-indicator"] = {
{"width", .05},
{"height", .04},
{"background", {255, 255, 255}},
{"foreground", {0, 0, 0}}
};
} }
void Configuration::load() void Configuration::load()

View File

@ -12,13 +12,27 @@ glm::ivec2 Display::get_window_size()
void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y)
{ {
GLenum format; if (get_root()->is_gl_context)
{
GLenum format;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
format = GL_RGB; format = GL_RGBA;
#else #else
format = GL_BGR; format = GL_BGRA;
#endif #endif
glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels);
}
else
{
Uint32 format;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
format = SDL_PIXELFORMAT_ABGR8888;
#else
format = SDL_PIXELFORMAT_ARGB8888;
#endif
SDL_RenderReadPixels(
get_root()->renderer, NULL, format, pixels, bpp / 8 * w);
}
} }
SDL_Surface* Display::get_screen_surface() SDL_Surface* Display::get_screen_surface()
@ -26,17 +40,28 @@ SDL_Surface* Display::get_screen_surface()
glm::ivec2 size = get_window_size(); glm::ivec2 size = get_window_size();
unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y]; unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y];
get_screen_pixels(pixels, size.x, size.y); get_screen_pixels(pixels, size.x, size.y);
SDL_Surface* surface = get_screen_surface_from_pixels(pixels); SDL_Surface* surface = get_screen_surface_from_pixels(
pixels, get_root()->is_gl_context);
delete[] pixels; delete[] pixels;
return surface; return surface;
} }
SDL_Surface* Display::get_screen_surface_from_pixels(unsigned char* pixels) SDL_Surface* Display::get_screen_surface_from_pixels(
unsigned char* pixels, bool flip)
{ {
glm::ivec2 size = get_window_size(); glm::ivec2 size = get_window_size();
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom( SDL_Surface* surface;
pixels, size.x, size.y, bpp, bpp / 8 * size.x, 0, 0, 0, 0); if (flip)
SDL_Surface* zoomed_surface = zoomSurface(surface, 1, -1, SMOOTHING_OFF); {
SDL_FreeSurface(surface); SDL_Surface* pixel_surface = SDL_CreateRGBSurfaceFrom(
return zoomed_surface; pixels, size.x, size.y, bpp, bpp / 8 * size.x, 0, 0, 0, 0);
surface = zoomSurface(pixel_surface, 1, -1, SMOOTHING_OFF);
SDL_FreeSurface(pixel_surface);
}
else
{
surface = SDL_CreateRGBSurface(0, size.x, size.y, bpp, 0, 0, 0, 0);
std::memcpy(surface->pixels, pixels, bpp / 8 * size.x * size.y);
}
return surface;
} }

View File

@ -19,13 +19,13 @@
struct Display : Node struct Display : Node
{ {
const static int bpp = 24; const static int bpp = 32;
Display(Node*); Display(Node*);
glm::ivec2 get_window_size(); glm::ivec2 get_window_size();
void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0); void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0);
SDL_Surface* get_screen_surface(); SDL_Surface* get_screen_surface();
SDL_Surface* get_screen_surface_from_pixels(unsigned char*); SDL_Surface* get_screen_surface_from_pixels(unsigned char*, bool);
}; };

View File

@ -1,14 +1,22 @@
#include "Game.hpp" #include "Game.hpp"
// FPSIndicator::FPSIndicator(Node* parent) : Node(parent) {}
// FPSIndicator::update()
// {
// }
Game::Game() Game::Game()
{ {
set_framerate(get_configuration()["display"]["fps"]);
delegate.subscribe(&Game::handle_quit_event, this, SDL_QUIT); delegate.subscribe(&Game::handle_quit_event, this, SDL_QUIT);
std::cout << "GLEW " << glewGetString(GLEW_VERSION) << std::endl; SDL_Log("GLEW %s", glewGetString(GLEW_VERSION));
putenv("SDL_VIDEO_X11_LEGACY_FULLSCREEN=0"); putenv("SDL_VIDEO_X11_LEGACY_FULLSCREEN=0");
putenv("SDL_VIDEO_CENTERED=1"); putenv("SDL_VIDEO_CENTERED=0");
SDL_version version; SDL_version version;
SDL_GetVersion(&version); SDL_GetVersion(&version);
printf("SDL %d.%d.%d\n", version.major, version.minor, version.patch); SDL_Log("SDL %d.%d.%d", version.major, version.minor, version.patch);
fprintf(stderr, "stderr test message\n"); fprintf(stderr, "stderr test message\n");
SDL_SetMainReady(); SDL_SetMainReady();
// SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); // SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
@ -24,8 +32,9 @@ Game::Game()
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
print_gl_attributes(); print_gl_attributes();
std::vector<int> window_size = get_configuration()["display"]["dimensions"];
window = SDL_CreateWindow("TARE control", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window = SDL_CreateWindow("TARE control", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
sw, sh, SDL_WINDOW_OPENGL); window_size[0], window_size[1], SDL_WINDOW_OPENGL);
if (window == NULL) if (window == NULL)
{ {
print_sdl_error("Could not create window"); print_sdl_error("Could not create window");
@ -39,8 +48,8 @@ Game::Game()
} }
else else
{ {
printf("initialized SDL ttf %d.%d.%d\n", SDL_TTF_MAJOR_VERSION, SDL_Log("initialized SDL ttf %d.%d.%d", SDL_TTF_MAJOR_VERSION,
SDL_TTF_MINOR_VERSION, SDL_TTF_PATCHLEVEL); SDL_TTF_MINOR_VERSION, SDL_TTF_PATCHLEVEL);
} }
if (Mix_Init(MIX_INIT_FLAC) == 0) if (Mix_Init(MIX_INIT_FLAC) == 0)
{ {
@ -49,22 +58,26 @@ Game::Game()
} }
else else
{ {
printf("initialized SDL mixer %d.%d.%d\n", SDL_MIXER_MAJOR_VERSION, SDL_Log("initialized SDL mixer %d.%d.%d", SDL_MIXER_MAJOR_VERSION,
SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL); SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL);
} }
if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
MIX_DEFAULT_CHANNELS, 1024) < 0) MIX_DEFAULT_CHANNELS, 1024) < 0)
{ {
print_sdl_error("Could not set up audio"); print_sdl_error("Could not set up audio");
} }
std::cout << "Using audio driver: " << SDL_GetCurrentAudioDriver() << SDL_Log("Using audio driver: %s", SDL_GetCurrentAudioDriver());
std::endl;
const int audio_device_count = SDL_GetNumAudioDevices(SDL_TRUE); const int audio_device_count = SDL_GetNumAudioDevices(SDL_TRUE);
for (int ii = 0; ii < audio_device_count; ii++) for (int ii = 0; ii < audio_device_count; ii++)
{ {
std::cout << "Found audio capture device " << ii << ": " << SDL_Log("Found audio capture device %i: %s", ii,
SDL_GetAudioDeviceName(ii, SDL_TRUE) << std::endl; SDL_GetAudioDeviceName(ii, SDL_TRUE));
} }
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
SDL_Log("big endian, using GL_RGBA and SDL_PIXELFORMAT_ABGR8888");
#else
SDL_Log("little endian, using GL_BGRA and SDL_PIXELFORMAT_ARGB8888");
#endif
last_frame_timestamp = SDL_GetTicks(); last_frame_timestamp = SDL_GetTicks();
} }
@ -88,7 +101,14 @@ void Game::print_gl_attributes()
int major, minor; int major, minor;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
std::cout << "GL CONTEXT: " << major << ", " << minor << std::endl; SDL_Log("GL CONTEXT: %i, %i", major, minor);
int r, g, b, a, buf;
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &r);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &g);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &b);
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &a);
SDL_GL_GetAttribute(SDL_GL_BUFFER_SIZE, &buf);
SDL_Log("GL PIXELS: red: %i green: %i blue: %i alpha: %i", r, g, b, a);
} }
void Game::load_sdl_context() void Game::load_sdl_context()
@ -103,6 +123,15 @@ void Game::load_sdl_context()
print_sdl_error("Could not create renderer"); print_sdl_error("Could not create renderer");
flag_to_end(); flag_to_end();
} }
else
{
SDL_RendererInfo info;
SDL_GetRendererInfo(renderer, &info);
SDL_Log("renderer name: %s, flags: %i, texture formats: %i, "
"max texture w: %i, max texture h: %i", info.name, info.flags,
info.num_texture_formats, info.max_texture_width,
info.max_texture_height);
}
is_gl_context = false; is_gl_context = false;
} }
@ -129,11 +158,212 @@ void Game::load_gl_context()
message << "GLEW could not initialize " << glewGetErrorString(error); message << "GLEW could not initialize " << glewGetErrorString(error);
print_error(message.str()); print_error(message.str());
} }
printf("OpenGL %s, renderer %s, shading language %s\n", glGetString(GL_VERSION), SDL_Log("OpenGL %s, renderer %s, shading language %s",
glGetString(GL_RENDERER), glGetString(GL_SHADING_LANGUAGE_VERSION)); glGetString(GL_VERSION), glGetString(GL_RENDERER),
glGetString(GL_SHADING_LANGUAGE_VERSION));
is_gl_context = true; is_gl_context = true;
} }
GLuint Game::create_gl_texture()
{
GLuint id;
glCreateTextures(GL_TEXTURE_2D, 1, &id);
glBindTexture(GL_TEXTURE_2D, id);
return id;
}
bool Game::log_gl_errors()
{
GLenum error;
bool error_logged = false;
while ((error = glGetError()) != GL_NO_ERROR)
{
error_logged = true;
if (error == GL_INVALID_ENUM)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_INVALID_ENUM, an unacceptable value is specified for an "
"enumerated argument");
}
else if (error == GL_INVALID_VALUE)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_INVALID_VALUE, a numeric argument is out of range");
}
else if (error == GL_INVALID_OPERATION)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_INVALID_OPERATION, the specified operation is not allowed "
"in the current state");
}
else if (error == GL_INVALID_FRAMEBUFFER_OPERATION)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_INVALID_FRAMEBUFFER_OPERATION, the framebuffer object is "
"not complete");
}
else if (error == GL_OUT_OF_MEMORY)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_OUT_OF_MEMORY, there is not enough memory left to execute "
"the command");
}
else if (error == GL_STACK_UNDERFLOW)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_STACK_UNDERFLOW, an attempt has been made to perform an "
"operation that would cause an internal stack to underflow.");
}
else if (error == GL_STACK_OVERFLOW)
{
SDL_LogError(
SDL_LOG_CATEGORY_APPLICATION,
"GL_STACK_OVERFLOW, an attempt has been made to perform an "
"operation that would cause an internal stack to overflow.");
}
}
return error_logged;
}
void Game::log_surface_format(SDL_Surface* surface, std::string preface)
{
SDL_PixelFormat* format = surface->format;
std::string pixel_format;
if (format->format == SDL_PIXELFORMAT_UNKNOWN)
{
pixel_format = "SDL_PIXELFORMAT_UNKNOWN";
}
else if (format->format == SDL_PIXELFORMAT_INDEX1LSB)
{
pixel_format = "SDL_PIXELFORMAT_INDEX1LSB";
}
else if (format->format == SDL_PIXELFORMAT_INDEX1MSB)
{
pixel_format = "SDL_PIXELFORMAT_INDEX1MSB";
}
else if (format->format == SDL_PIXELFORMAT_INDEX4LSB)
{
pixel_format = "SDL_PIXELFORMAT_INDEX4LSB";
}
else if (format->format == SDL_PIXELFORMAT_INDEX4MSB)
{
pixel_format = "SDL_PIXELFORMAT_INDEX4MSB";
}
else if (format->format == SDL_PIXELFORMAT_INDEX8)
{
pixel_format = "SDL_PIXELFORMAT_INDEX8";
}
else if (format->format == SDL_PIXELFORMAT_RGB332)
{
pixel_format = "SDL_PIXELFORMAT_RGB332";
}
else if (format->format == SDL_PIXELFORMAT_RGB444)
{
pixel_format = "SDL_PIXELFORMAT_RGB444";
}
else if (format->format == SDL_PIXELFORMAT_RGB555)
{
pixel_format = "SDL_PIXELFORMAT_RGB555";
}
else if (format->format == SDL_PIXELFORMAT_BGR555)
{
pixel_format = "SDL_PIXELFORMAT_BGR555";
}
else if (format->format == SDL_PIXELFORMAT_ARGB4444)
{
pixel_format = "SDL_PIXELFORMAT_ARGB4444";
}
else if (format->format == SDL_PIXELFORMAT_RGBA4444)
{
pixel_format = "SDL_PIXELFORMAT_RGBA4444";
}
else if (format->format == SDL_PIXELFORMAT_ABGR4444)
{
pixel_format = "SDL_PIXELFORMAT_ABGR4444";
}
else if (format->format == SDL_PIXELFORMAT_BGRA4444)
{
pixel_format = "SDL_PIXELFORMAT_BGRA4444";
}
else if (format->format == SDL_PIXELFORMAT_ARGB1555)
{
pixel_format = "SDL_PIXELFORMAT_ARGB1555";
}
else if (format->format == SDL_PIXELFORMAT_RGBA5551)
{
pixel_format = "SDL_PIXELFORMAT_RGBA5551";
}
else if (format->format == SDL_PIXELFORMAT_ABGR1555)
{
pixel_format = "SDL_PIXELFORMAT_ABGR1555";
}
else if (format->format == SDL_PIXELFORMAT_BGRA5551)
{
pixel_format = "SDL_PIXELFORMAT_BGRA5551";
}
else if (format->format == SDL_PIXELFORMAT_RGB565)
{
pixel_format = "SDL_PIXELFORMAT_RGB565";
}
else if (format->format == SDL_PIXELFORMAT_BGR565)
{
pixel_format = "SDL_PIXELFORMAT_BGR565";
}
else if (format->format == SDL_PIXELFORMAT_RGB24)
{
pixel_format = "SDL_PIXELFORMAT_RGB24";
}
else if (format->format == SDL_PIXELFORMAT_BGR24)
{
pixel_format = "SDL_PIXELFORMAT_BGR24";
}
else if (format->format == SDL_PIXELFORMAT_RGB888)
{
pixel_format = "SDL_PIXELFORMAT_RGB888";
}
else if (format->format == SDL_PIXELFORMAT_RGBX8888)
{
pixel_format = "SDL_PIXELFORMAT_RGBX8888";
}
else if (format->format == SDL_PIXELFORMAT_BGR888)
{
pixel_format = "SDL_PIXELFORMAT_BGR888";
}
else if (format->format == SDL_PIXELFORMAT_BGRX8888)
{
pixel_format = "SDL_PIXELFORMAT_BGRX8888";
}
else if (format->format == SDL_PIXELFORMAT_ARGB8888)
{
pixel_format = "SDL_PIXELFORMAT_ARGB8888";
}
else if (format->format == SDL_PIXELFORMAT_RGBA8888)
{
pixel_format = "SDL_PIXELFORMAT_RGBA8888";
}
else if (format->format == SDL_PIXELFORMAT_ABGR8888)
{
pixel_format = "SDL_PIXELFORMAT_ABGR8888";
}
else if (format->format == SDL_PIXELFORMAT_BGRA8888)
{
pixel_format = "SDL_PIXELFORMAT_BGRA8888";
}
else if (format->format == SDL_PIXELFORMAT_ARGB2101010)
{
pixel_format = "SDL_PIXELFORMAT_ARGB2101010";
}
SDL_Log("%s bpp: %i mask: %i %i %i %i format: %s", preface.c_str(),
format->BytesPerPixel, format->Rmask, format->Gmask, format->Bmask,
format->Amask, pixel_format.c_str());
}
void Game::run() void Game::run()
{ {
while (not done) while (not done)

View File

@ -21,6 +21,14 @@
#include "Display.hpp" #include "Display.hpp"
#include "Recorder.hpp" #include "Recorder.hpp"
#include "Input.hpp" #include "Input.hpp"
// #include "Sprite.hpp"
// struct FPSIndicator : Sprite
// {
// FPSIndicator(Node*);
// };
struct Game : Node struct Game : Node
{ {
@ -33,10 +41,11 @@ struct Game : Node
SDL_Window* window; SDL_Window* window;
SDL_Renderer* renderer = NULL; SDL_Renderer* renderer = NULL;
SDL_GLContext glcontext = NULL; SDL_GLContext glcontext = NULL;
int sw = 640, sh = 480, framerate = 60, frame_time_overflow = 0, // 768, 432
last_frame_timestamp, frame_count_timestamp, ticks, // 864, 486
last_frame_length; int frame_time_overflow = 0, framerate, ticks, last_frame_timestamp,
float frame_length = 1000.0 / framerate; frame_count_timestamp, last_frame_length;
float frame_length;
bool done = false, show_framerate = false, is_gl_context = true; bool done = false, show_framerate = false, is_gl_context = true;
Configuration configuration = Configuration(this); Configuration configuration = Configuration(this);
Delegate delegate = Delegate(this); Delegate delegate = Delegate(this);
@ -51,6 +60,9 @@ struct Game : Node
void print_gl_attributes(); void print_gl_attributes();
void load_sdl_context(); void load_sdl_context();
void load_gl_context(); void load_gl_context();
GLuint create_gl_texture();
bool log_gl_errors();
void log_surface_format(SDL_Surface*, std::string = "surface");
void run(); void run();
void flag_to_end(); void flag_to_end();
virtual void update() {}; virtual void update() {};
@ -67,4 +79,6 @@ struct Game : Node
}; };
#include "Sprite.hpp"
#endif #endif

View File

@ -75,23 +75,20 @@ void Input::add_to_key_map(
void Input::respond(SDL_Event &event) void Input::respond(SDL_Event &event)
{ {
// if (event.type == SDL_KEYDOWN) SDL_Keymod mod = SDL_GetModState();
// { for (KeyCombination& combination : key_map)
SDL_Keymod mod = SDL_GetModState(); {
for (KeyCombination& combination : key_map) if (event.key.keysym.sym == combination.key and
(not combination.ctrl || mod & KMOD_CTRL) and
(not combination.shift || mod & KMOD_SHIFT) and
(not combination.alt || mod & KMOD_ALT))
{ {
if (event.key.keysym.sym == combination.key and SDL_Event relay;
(not combination.ctrl || mod & KMOD_CTRL) and bool* cancel = new bool(event.type == SDL_KEYDOWN ? false : true);
(not combination.shift || mod & KMOD_SHIFT) and relay.type = Delegate::command_event_type;
(not combination.alt || mod & KMOD_ALT)) relay.user.data1 = &combination.command;
{ relay.user.data2 = cancel;
SDL_Event relay; SDL_PushEvent(&relay);
bool* cancel = new bool(event.type == SDL_KEYDOWN ? false : true);
relay.type = Delegate::command_event_type;
relay.user.data1 = &combination.command;
relay.user.data2 = cancel;
SDL_PushEvent(&relay);
}
} }
// } }
} }

View File

@ -1,12 +1,16 @@
#include "Node.hpp" #include "Node.hpp"
#include "Game.hpp" #include "Game.hpp"
#include "Display.hpp" // #include "Display.hpp"
#include "Delegate.hpp" // #include "Delegate.hpp"
Node::Node() : Node(NULL) {} Node::Node() : Node(NULL) {}
Node::Node(Node *parent) : parent(parent) Node::Node(Node *parent, bool active) : parent(parent)
{ {
if (active)
{
activate();
}
std::cout << "Constructing "; std::cout << "Constructing ";
print_branch(); print_branch();
} }
@ -18,6 +22,16 @@ Node::~Node()
get_delegate().unsubscribe(this); get_delegate().unsubscribe(this);
} }
void Node::activate()
{
active = true;
}
void Node::deactivate()
{
active = false;
}
nlohmann::json& Node::get_configuration() nlohmann::json& Node::get_configuration()
{ {
return get_root()->configuration.config; return get_root()->configuration.config;

View File

@ -17,10 +17,13 @@ struct Node
{ {
Node *parent = NULL; Node *parent = NULL;
bool active = false;
Node(); Node();
Node(Node*); Node(Node*, bool = false);
virtual ~Node(); virtual ~Node();
void activate();
void deactivate();
Game* get_root(); Game* get_root();
nlohmann::json& get_configuration(); nlohmann::json& get_configuration();
Delegate& get_delegate(); Delegate& get_delegate();

View File

@ -133,7 +133,7 @@ void Recorder::open_audio_file()
void Recorder::add_frame() void Recorder::add_frame()
{ {
glm::ivec2 size = get_display().get_window_size(); glm::ivec2 size = get_display().get_window_size();
int bytes = Display::bpp / 8 * size.x * size.y; int bytes = 32 / 8 * size.x * size.y;
unsigned char* pixels = new unsigned char[bytes]; unsigned char* pixels = new unsigned char[bytes];
get_display().get_screen_pixels(pixels, size.x, size.y); get_display().get_screen_pixels(pixels, size.x, size.y);
int max_length = get_configuration()["recording"]["max-stash-length"]; int max_length = get_configuration()["recording"]["max-stash-length"];
@ -142,13 +142,16 @@ void Recorder::add_frame()
{ {
delete[] current_stash.pixel_buffers.front(); delete[] current_stash.pixel_buffers.front();
current_stash.pixel_buffers.erase(current_stash.pixel_buffers.begin()); current_stash.pixel_buffers.erase(current_stash.pixel_buffers.begin());
current_stash.flipped.erase(current_stash.flipped.begin());
} }
current_stash.pixel_buffers.push_back(pixels); current_stash.pixel_buffers.push_back(pixels);
current_stash.flipped.push_back(get_root()->is_gl_context);
if (is_recording) if (is_recording)
{ {
unsigned char* vid_pixels = new unsigned char[bytes]; unsigned char* vid_pixels = new unsigned char[bytes];
memcpy(vid_pixels, pixels, bytes); memcpy(vid_pixels, pixels, bytes);
video_stashes.back().pixel_buffers.push_back(vid_pixels); video_stashes.back().pixel_buffers.push_back(vid_pixels);
video_stashes.back().flipped.push_back(get_root()->is_gl_context);
if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length) if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length)
{ {
std::function<void(bool, int)> f = std::function<void(bool, int)> f =
@ -166,20 +169,30 @@ void Recorder::add_frame()
int Recorder::get_memory_size() int Recorder::get_memory_size()
{ {
glm::ivec2 window = get_display().get_window_size(); glm::ivec2 window = get_display().get_window_size();
int bytes_per_buffer = Display::bpp / 8 * window.x * window.y; int bytes_per_frame = Display::bpp / 8 * window.x * window.y,
int count = 0; size_in_bytes = 0;
for (Stash& stash : in_game_stashes) for (Stash& stash : in_game_stashes)
{ {
count += stash.pixel_buffers.size(); size_in_bytes += stash.pixel_buffers.size() * bytes_per_frame;
for (int& length : stash.audio_buffer_lengths)
{
size_in_bytes += length;
}
} }
for (Stash& stash : video_stashes) for (Stash& stash : video_stashes)
{ {
count += stash.pixel_buffers.size(); size_in_bytes += stash.pixel_buffers.size() * bytes_per_frame;
}
size_in_bytes += current_stash.pixel_buffers.size() * bytes_per_frame;
for (int& length : current_stash.audio_buffer_lengths)
{
size_in_bytes += length;
}
size_in_bytes += most_recent_stash.pixel_buffers.size() * bytes_per_frame;
for (int& length : most_recent_stash.audio_buffer_lengths)
{
size_in_bytes += length;
} }
count += current_stash.pixel_buffers.size();
count += most_recent_stash.pixel_buffers.size();
int size_in_bytes = 0;
size_in_bytes = count * bytes_per_buffer;
return size_in_bytes / 1000000; return size_in_bytes / 1000000;
} }
@ -196,8 +209,7 @@ void Recorder::make_directory()
void Recorder::write_stash_frames(bool is_video, int index) void Recorder::write_stash_frames(bool is_video, int index)
{ {
int frame_offset = is_video ? video_stashes[index].frame_offset : 0; int frame_offset = is_video ? video_stashes[index].frame_offset : 0;
std::cout << "Writing stash index " << index << " to " << current_video_directory << "..." << SDL_Log("Writing stash index %i to %s...", index, current_video_directory.c_str());
std::endl;
SDL_Surface* frame; SDL_Surface* frame;
GifWriter gif_writer; GifWriter gif_writer;
int gif_frame_length = get_configuration()["recording"]["gif-frame-length"]; int gif_frame_length = get_configuration()["recording"]["gif-frame-length"];
@ -211,12 +223,14 @@ void Recorder::write_stash_frames(bool is_video, int index)
if (is_video) if (is_video)
{ {
frame = get_display().get_screen_surface_from_pixels( frame = get_display().get_screen_surface_from_pixels(
video_stashes[index].pixel_buffers.front()); video_stashes[index].pixel_buffers.front(),
video_stashes[index].flipped.front());
} }
else else
{ {
frame = get_display().get_screen_surface_from_pixels( frame = get_display().get_screen_surface_from_pixels(
most_recent_stash.pixel_buffers.front()); most_recent_stash.pixel_buffers.front(),
most_recent_stash.flipped.front());
} }
std::stringstream name; std::stringstream name;
name << sfw::pad(ii, 5) << ".png"; name << sfw::pad(ii, 5) << ".png";
@ -235,7 +249,9 @@ void Recorder::write_stash_frames(bool is_video, int index)
gif_write_overflow += elapsed - (last_gif_write + gif_frame_length); gif_write_overflow += elapsed - (last_gif_write + gif_frame_length);
last_gif_write = elapsed; last_gif_write = elapsed;
} }
GifWriteFrame(&gif_writer, (const uint8_t*) frame->pixels, SDL_Surface* converted = SDL_ConvertSurfaceFormat(
frame, SDL_PIXELFORMAT_ABGR8888, 0);
GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels,
frame->w, frame->h, gif_frame_length / 10); frame->w, frame->h, gif_frame_length / 10);
} }
elapsed += get_frame_length(); elapsed += get_frame_length();
@ -244,12 +260,16 @@ void Recorder::write_stash_frames(bool is_video, int index)
delete[] video_stashes[index].pixel_buffers.front(); delete[] video_stashes[index].pixel_buffers.front();
video_stashes[index].pixel_buffers.erase( video_stashes[index].pixel_buffers.erase(
video_stashes[index].pixel_buffers.begin()); video_stashes[index].pixel_buffers.begin());
video_stashes[index].flipped.erase(
video_stashes[index].flipped.begin());
} }
else else
{ {
delete[] most_recent_stash.pixel_buffers.front(); delete[] most_recent_stash.pixel_buffers.front();
most_recent_stash.pixel_buffers.erase( most_recent_stash.pixel_buffers.erase(
most_recent_stash.pixel_buffers.begin()); most_recent_stash.pixel_buffers.begin());
most_recent_stash.flipped.erase(
most_recent_stash.flipped.begin());
} }
SDL_FreeSurface(frame); SDL_FreeSurface(frame);
} }
@ -268,6 +288,7 @@ void Recorder::keep_stash()
{ {
delete[] stash.pixel_buffers.back(); delete[] stash.pixel_buffers.back();
stash.pixel_buffers.pop_back(); stash.pixel_buffers.pop_back();
stash.flipped.pop_back();
} }
in_game_stashes.erase(in_game_stashes.begin()); in_game_stashes.erase(in_game_stashes.begin());
} }

View File

@ -23,6 +23,7 @@
struct Stash struct Stash
{ {
std::vector<unsigned char*> pixel_buffers; std::vector<unsigned char*> pixel_buffers;
std::vector<bool> flipped;
std::vector<Uint8*> audio_buffers; std::vector<Uint8*> audio_buffers;
std::vector<int> audio_buffer_lengths; std::vector<int> audio_buffer_lengths;
int frame_offset; int frame_offset;

View File

@ -1,11 +1,17 @@
#include "Sprite.hpp" #include "Sprite.hpp"
#include "Game.hpp" #include "Game.hpp"
Sprite::Sprite(Node *parent) : Node(parent) {} Sprite::Sprite(Node *parent) : Node(parent, true) {}
Sprite::Sprite(Node *parent, std::string path) : Node(parent) Sprite::Sprite(Node *parent, std::string path) : Node(parent, true)
{ {
associate(path); associate(path);
animation.play();
}
void Sprite::set_frame_length(float length)
{
animation.set_frame_length(length);
} }
void Sprite::associate(std::string path) void Sprite::associate(std::string path)
@ -70,15 +76,23 @@ void Sprite::unload()
} }
} }
void Sprite::update() void Sprite::advance_frame()
{ {
int w, h; if (++frame_ii >= frames.size())
SDL_Texture *frame = frames[frame_ii++];
SDL_QueryTexture(frame, NULL, NULL, &w, &h);
SDL_Rect frame_rect = {location.get_x(), location.get_y(), w, h};
SDL_RenderCopy(get_root()->renderer, frame, NULL, &frame_rect);
if (frame_ii >= frames.size())
{ {
frame_ii = 0; frame_ii = 0;
} }
} }
void Sprite::update()
{
if (active)
{
animation.update();
int w, h;
SDL_Texture *frame = frames[frame_ii];
SDL_QueryTexture(frame, NULL, NULL, &w, &h);
SDL_Rect frame_rect = {location.get_x(), location.get_y(), w, h};
SDL_RenderCopy(get_root()->renderer, frame, NULL, &frame_rect);
}
}

View File

@ -12,6 +12,7 @@
#include "Node.hpp" #include "Node.hpp"
#include "Game.hpp" #include "Game.hpp"
#include "Location.hpp" #include "Location.hpp"
#include "Animation.hpp"
struct Sprite : Node struct Sprite : Node
{ {
@ -20,14 +21,17 @@ struct Sprite : Node
std::vector<fs::path> frame_paths; std::vector<fs::path> frame_paths;
int frame_ii = 0; int frame_ii = 0;
Location location; Location location;
Animation animation = Animation(&Sprite::advance_frame, this);
Sprite(Node*); Sprite(Node*);
Sprite(Node*, std::string); Sprite(Node*, std::string);
void set_frame_length(float);
void associate(std::string); void associate(std::string);
void load(); void load();
void load_file(fs::path); void load_file(fs::path);
void add_frame(SDL_Texture*); void add_frame(SDL_Texture*);
void unload(); void unload();
void advance_frame();
void update(); void update();
std::string get_class_name() { return "Sprite"; } std::string get_class_name() { return "Sprite"; }