Browse Source

initial commit hacked out from seni/gui

master
Inderjit Gill 7 months ago
commit
b1e6a03903
23 changed files with 1754 additions and 0 deletions
  1. +4
    -0
      .gitignore
  2. +19
    -0
      Cargo.toml
  3. +9
    -0
      Config.toml.example
  4. +8
    -0
      README.md
  5. BIN
      SDL2.dll
  6. +16
    -0
      assets/shaders/blit.frag
  7. +17
    -0
      assets/shaders/blit.vert
  8. +13
    -0
      assets/shaders/imgui.frag
  9. +17
    -0
      assets/shaders/imgui.vert
  10. +21
    -0
      assets/shaders/piece.frag
  11. +20
    -0
      assets/shaders/piece.vert
  12. +3
    -0
      lib/gl/.gitignore
  13. +13
    -0
      lib/gl/Cargo.toml
  14. +22
    -0
      lib/gl/build.rs
  15. +33
    -0
      lib/gl/src/lib.rs
  16. +96
    -0
      src/error.rs
  17. +174
    -0
      src/gl_util.rs
  18. +229
    -0
      src/input_imgui.rs
  19. +211
    -0
      src/main.rs
  20. +67
    -0
      src/matrix_util.rs
  21. +214
    -0
      src/render_gl.rs
  22. +314
    -0
      src/render_imgui.rs
  23. +234
    -0
      src/render_square.rs

+ 4
- 0
.gitignore View File

@@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
Config.toml

+ 19
- 0
Cargo.toml View File

@@ -0,0 +1,19 @@
[package]
name = "depict"
version = "0.1.0"
authors = ["Inderjit Gill <email@indy.io>"]
edition = "2018"
license = "GPL-3.0+"

[dependencies]
clap = "2.32.0"
config = "0.9"
env_logger = "0.6"
gl = { path = "lib/gl" }
image = "0.21.1"
imgui = "0.0.22"
log = "0.4"
sdl2 = "0.32.1"

[features]
gl_debug = ["gl/debug"]

+ 9
- 0
Config.toml.example View File

@@ -0,0 +1,9 @@
# the default script if none is supplied on the command line
script = "1918-einstein.seni"

assets_path = "/home/indy/code/seni/gui/assets"
scripts_path = "/home/indy/code/seni/server/static/seni"
bitmaps_path = "/home/indy/code/seni/client/www/img"

profiling = false
debug = false

+ 8
- 0
README.md View File

@@ -0,0 +1,8 @@
## Setup

Copy the Config.toml.example file as Config.toml


## Build

On Windows when you're shipping your game make sure to copy SDL2.dll to the same directory that your compiled exe is in, otherwise the game won't launch.

BIN
SDL2.dll View File


+ 16
- 0
assets/shaders/blit.frag View File

@@ -0,0 +1,16 @@
#version 330 core

// in VS_OUTPUT {
// vec2 TextureCoord;
// } IN;

// Values that stay constant for the whole mesh.
// uniform sampler2D myTextureSampler;

out vec4 Colour;

void main()
{
// Colour = texture( myTextureSampler, IN.TextureCoord );
Colour = vec4(1.0, 0.0, 0.0, 1.0);
}

+ 17
- 0
assets/shaders/blit.vert View File

@@ -0,0 +1,17 @@
#version 330 core

in vec2 Position;
in vec2 UV;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

// out VS_OUTPUT {
// vec2 TextureCoord;
// } OUT;

void main()
{
gl_Position = uPMatrix * uMVMatrix * vec4(Position, 0.0, 1.0);
// OUT.TextureCoord = UV;
}

+ 13
- 0
assets/shaders/imgui.frag View File

@@ -0,0 +1,13 @@
#version 130

uniform sampler2D Texture;

in vec2 Frag_UV;
in vec4 Frag_Color;

out vec4 Out_Color;

void main()
{
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
}

+ 17
- 0
assets/shaders/imgui.vert View File

@@ -0,0 +1,17 @@
#version 130

uniform mat4 ProjMtx;

in vec2 Position;
in vec2 UV;
in vec4 Color;

out vec2 Frag_UV;
out vec4 Frag_Color;

void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy,0,1);
}

+ 21
- 0
assets/shaders/piece.frag View File

@@ -0,0 +1,21 @@
#version 330 core

in VS_OUTPUT {
vec4 Colour;
vec2 TextureCoord;
} IN;

// Values that stay constant for the whole mesh.
uniform sampler2D myTextureSampler;

layout(location = 0) out vec4 Colour;

void main()
{
vec4 tex = texture( myTextureSampler, IN.TextureCoord );

Colour.r = tex.r * IN.Colour.r;
Colour.g = tex.r * IN.Colour.g;
Colour.b = tex.r * IN.Colour.b;
Colour.a = tex.r * IN.Colour.a;
}

+ 20
- 0
assets/shaders/piece.vert View File

@@ -0,0 +1,20 @@
#version 330 core

in vec2 Position;
in vec4 Colour;
in vec2 UV;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

out VS_OUTPUT {
vec4 Colour;
vec2 TextureCoord;
} OUT;

void main()
{
gl_Position = uPMatrix * uMVMatrix * vec4(Position, 0.0, 1.0);
OUT.Colour = Colour;
OUT.TextureCoord = UV;
}

+ 3
- 0
lib/gl/.gitignore View File

@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

+ 13
- 0
lib/gl/Cargo.toml View File

@@ -0,0 +1,13 @@
[package]
name = "gl"
version = "4.1.0"
authors = ["Inderjit Gill <email@indy.io>"]
edition = "2018"
license = "GPL-3.0+"

[build-dependencies]
gl_generator = "0.9"
gl_generator_profiling_struct = "0.1"

[features]
debug = []

+ 22
- 0
lib/gl/build.rs View File

@@ -0,0 +1,22 @@
use gl_generator::{Api, Fallbacks, Profile, Registry};
use gl_generator_profiling_struct::ProfilingStructGenerator;
use std::env;
use std::fs::File;
use std::path::Path;

fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let mut file_gl = File::create(&Path::new(&out_dir).join("bindings.rs")).unwrap();

let registry = Registry::new(
Api::Gl,
(4, 5),
Profile::Core,
Fallbacks::All,
["GL_NV_command_list"],
);

registry
.write_bindings(ProfilingStructGenerator, &mut file_gl)
.unwrap();
}

+ 33
- 0
lib/gl/src/lib.rs View File

@@ -0,0 +1,33 @@
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

use std::ops::Deref;
use std::rc::Rc;

pub use crate::bindings::Gl as InnerGl;
pub use crate::bindings::*;

#[derive(Clone)]
pub struct Gl {
inner: Rc<bindings::Gl>,
}

impl Gl {
pub fn load_with<F>(loadfn: F) -> Gl
where
F: FnMut(&'static str) -> *const types::GLvoid,
{
Gl {
inner: Rc::new(bindings::Gl::load_with(loadfn)),
}
}
}

impl Deref for Gl {
type Target = bindings::Gl;

fn deref(&self) -> &bindings::Gl {
&self.inner
}
}

+ 96
- 0
src/error.rs View File

@@ -0,0 +1,96 @@
// Copyright (C) 2019 Inderjit Gill

// 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 3 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, see <https://www.gnu.org/licenses/>.

use std::error;
use std::fmt;

pub type Result<T> = ::std::result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
Placeholder(String), // temp placeholder error

GLError(String),
ConfigError(config::ConfigError),
StringError(String),
SDL2WindowBuildError(sdl2::video::WindowBuildError),
FFINulError(std::ffi::NulError),
Io(std::io::Error),
ImageError(image::ImageError),
FileContainsNil,
AssetLoad { name: String },
CanNotDetermineShaderType { name: String },
CompileError { name: String, message: String },
LinkError { name: String, message: String },
}

impl From<config::ConfigError> for Error {
fn from(e: config::ConfigError) -> Error {
Error::ConfigError(e)
}
}

impl From<String> for Error {
fn from(e: String) -> Error {
Error::StringError(e)
}
}

impl From<sdl2::video::WindowBuildError> for Error {
fn from(e: sdl2::video::WindowBuildError) -> Error {
Error::SDL2WindowBuildError(e)
}
}

impl From<std::ffi::NulError> for Error {
fn from(e: std::ffi::NulError) -> Error {
Error::FFINulError(e)
}
}

impl From<std::io::Error> for Error {
fn from(other: std::io::Error) -> Self {
Error::Io(other)
}
}

impl From<image::ImageError> for Error {
fn from(other: image::ImageError) -> Self {
Error::ImageError(other)
}
}

// don't need to implement any of the trait's methods
impl error::Error for Error {}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Placeholder(s) => write!(f, "seni gui: Placeholder: {:?}", s),
Error::GLError(s) => write!(f, "seni gui: OpenGL Error: {:?}", s),
Error::ConfigError(c) => write!(f, "seni gui: Config Error: {:?}", c),
Error::StringError(s) => write!(f, "seni gui: String Error: {}", s),
Error::SDL2WindowBuildError(e) => write!(f, "seni gui: SDL2WindowBuildError: {:?}", e),
Error::FFINulError(e) => write!(f, "seni gui: std::ffi:NulError: {:?}", e),
Error::Io(e) => write!(f, "seni gui: Io: {:?}", e),
Error::ImageError(e) => write!(f, "seni gui: ImageError: {:?}", e),
Error::FileContainsNil => write!(f, "seni gui: FileContainsNil"),
Error::AssetLoad { name } => write!(f, "seni gui: {}", name),
Error::CanNotDetermineShaderType { name } => write!(f, "seni gui: {}", name),
Error::CompileError { name, message } => write!(f, "seni gui: {} {}", name, message),
Error::LinkError { name, message } => write!(f, "seni gui: {} {}", name, message),
}
}
}

+ 174
- 0
src/gl_util.rs View File

@@ -0,0 +1,174 @@
// Copyright (C) 2019 Inderjit Gill

// 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 3 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, see <https://www.gnu.org/licenses/>.

use std::mem;

use std::path::Path;

use gl;
use image::GenericImageView;
use log::info;

use crate::error::{Error, Result};


#[derive(Default)]
pub struct BitmapInfo {
pub width: usize,
pub height: usize,
pub data: Vec<u8>,
}


#[macro_export]
macro_rules! c_str {
($literal:expr) => {
std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($literal, "\0").as_bytes())
};
}

pub fn return_param<T, F>(f: F) -> T
where
F: FnOnce(&mut T),
{
let mut val = unsafe { mem::uninitialized() };
f(&mut val);
val
}

pub fn load_texture(ppath: &Path, name: &str) -> Result<BitmapInfo> {
let path = ppath.join(name);

info!("load_bitmap: {:?}", path);
let image = image::open(&path)?;

let (w, h) = image.dimensions();
let width = w as usize;
let height = h as usize;
let mut data: Vec<u8> = Vec::with_capacity(width * height * 4);

info!("loading bitmap {} of size {} x {}", name, width, height);

for (_, _, rgba) in image.pixels() {
data.push(rgba.data[0]);
data.push(rgba.data[1]);
data.push(rgba.data[2]);
data.push(rgba.data[3]);
}

let mut data_flipped: Vec<u8> = Vec::with_capacity(width * height * 4);
for y in 0..height {
for x in 0..width {
let offset = ((height - y - 1) * (width * 4)) + (x * 4);
data_flipped.push(data[offset]);
data_flipped.push(data[offset + 1]);
data_flipped.push(data[offset + 2]);
data_flipped.push(data[offset + 3]);
}
}

let bitmap_info = BitmapInfo {
width,
height,
data: data_flipped,
..Default::default()
};

Ok(bitmap_info)
}

pub fn create_framebuffer(gl: &gl::Gl) -> gl::types::GLuint {
let mut framebuffer_id: gl::types::GLuint = 0;

unsafe {
gl.GenFramebuffers(1, &mut framebuffer_id);
}

framebuffer_id
}

pub fn create_texture(gl: &gl::Gl, width: usize, height: usize) -> gl::types::GLuint {
let mut texture_id: gl::types::GLuint = 0;

unsafe {
gl.GenTextures(1, &mut texture_id);
// "Bind" the newly created texture : all future texture functions will modify this texture
gl.BindTexture(gl::TEXTURE_2D, texture_id);
// Give an empty image to OpenGL ( the last "0" )
gl.TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as _,
width as _,
height as _,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
0 as *const gl::types::GLvoid,
);

gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _);
gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _);
}

texture_id
}

pub fn attach_texture_to_framebuffer(
gl: &gl::Gl,
framebuffer_id: gl::types::GLuint,
texture_id: gl::types::GLuint,
) {
unsafe {
gl.BindFramebuffer(gl::FRAMEBUFFER, framebuffer_id);
gl.FramebufferTexture2D(
gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture_id,
0,
);
}
}

pub fn is_framebuffer_ok(gl: &gl::Gl) -> Result<()> {
unsafe {
if gl.CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE {
Err(Error::GLError("Framebuffer is not complete".to_string()))
} else {
Ok(())
}
}
}

pub fn bind_framebuffer(
gl: &gl::Gl,
framebuffer_id: gl::types::GLuint,
viewport_width: usize,
viewport_height: usize,
) {
unsafe {
gl.BindFramebuffer(gl::FRAMEBUFFER, framebuffer_id);
gl.Viewport(0, 0, viewport_width as _, viewport_height as _);
}
}

pub fn update_viewport(gl: &gl::Gl, viewport_width: usize, viewport_height: usize) {
unsafe {
gl.Viewport(0, 0, viewport_width as _, viewport_height as _);
}
}

+ 229
- 0
src/input_imgui.rs View File

@@ -0,0 +1,229 @@
// code from:
// https://github.com/michaelfairley/rust-imgui-sdl2
// licensed under MIT/Apache 2.0
//
use imgui::sys as imgui_sys;
use sdl2::sys as sdl2_sys;

use imgui::{ImGui, ImGuiMouseCursor};
use sdl2::mouse::{Cursor, MouseState, SystemCursor};
use sdl2::video::Window;
use std::os::raw::{c_char, c_void};
use std::time::Instant;

use sdl2::event::Event;

pub struct ImguiSdl2 {
last_frame: Instant,
mouse_press: [bool; 5],
ignore_mouse: bool,
ignore_keyboard: bool,
cursor: (ImGuiMouseCursor, Option<Cursor>),
}

impl ImguiSdl2 {
pub fn new(imgui: &mut ImGui) -> Self {
// TODO: upstream to imgui-rs
{
let io = unsafe { &mut *imgui_sys::igGetIO() };

io.get_clipboard_text_fn = Some(get_clipboard_text);
io.set_clipboard_text_fn = Some(set_clipboard_text);
io.clipboard_user_data = std::ptr::null_mut();
}

{
use imgui::ImGuiKey;
use sdl2::keyboard::Scancode;

imgui.set_imgui_key(ImGuiKey::Tab, Scancode::Tab as u8);
imgui.set_imgui_key(ImGuiKey::LeftArrow, Scancode::Left as u8);
imgui.set_imgui_key(ImGuiKey::RightArrow, Scancode::Right as u8);
imgui.set_imgui_key(ImGuiKey::UpArrow, Scancode::Up as u8);
imgui.set_imgui_key(ImGuiKey::DownArrow, Scancode::Down as u8);
imgui.set_imgui_key(ImGuiKey::PageUp, Scancode::PageUp as u8);
imgui.set_imgui_key(ImGuiKey::PageDown, Scancode::PageDown as u8);
imgui.set_imgui_key(ImGuiKey::Home, Scancode::Home as u8);
imgui.set_imgui_key(ImGuiKey::End, Scancode::End as u8);
imgui.set_imgui_key(ImGuiKey::Delete, Scancode::Delete as u8);
imgui.set_imgui_key(ImGuiKey::Backspace, Scancode::Backspace as u8);
imgui.set_imgui_key(ImGuiKey::Enter, Scancode::Return as u8);
imgui.set_imgui_key(ImGuiKey::Escape, Scancode::Escape as u8);
imgui.set_imgui_key(ImGuiKey::A, Scancode::A as u8);
imgui.set_imgui_key(ImGuiKey::C, Scancode::C as u8);
imgui.set_imgui_key(ImGuiKey::V, Scancode::V as u8);
imgui.set_imgui_key(ImGuiKey::X, Scancode::X as u8);
imgui.set_imgui_key(ImGuiKey::Y, Scancode::Y as u8);
imgui.set_imgui_key(ImGuiKey::Z, Scancode::Z as u8);
}

Self {
last_frame: Instant::now(),
mouse_press: [false; 5],
ignore_keyboard: false,
ignore_mouse: false,
cursor: (ImGuiMouseCursor::None, None),
}
}

pub fn ignore_event(&self, event: &Event) -> bool {
match *event {
Event::KeyDown { .. }
| Event::KeyUp { .. }
| Event::TextEditing { .. }
| Event::TextInput { .. } => self.ignore_keyboard,
Event::MouseMotion { .. }
| Event::MouseButtonDown { .. }
| Event::MouseButtonUp { .. }
| Event::MouseWheel { .. }
| Event::FingerDown { .. }
| Event::FingerUp { .. }
| Event::FingerMotion { .. }
| Event::DollarGesture { .. }
| Event::DollarRecord { .. }
| Event::MultiGesture { .. } => self.ignore_mouse,
_ => false,
}
}

pub fn handle_event(&mut self, imgui: &mut ImGui, event: &Event) {
use sdl2::keyboard;
use sdl2::mouse::MouseButton;

fn set_mod(imgui: &mut ImGui, keymod: keyboard::Mod) {
let ctrl = keymod.intersects(keyboard::Mod::RCTRLMOD | keyboard::Mod::LCTRLMOD);
let alt = keymod.intersects(keyboard::Mod::RALTMOD | keyboard::Mod::LALTMOD);
let shift = keymod.intersects(keyboard::Mod::RSHIFTMOD | keyboard::Mod::LSHIFTMOD);
let super_ = keymod.intersects(keyboard::Mod::RGUIMOD | keyboard::Mod::LGUIMOD);

imgui.set_key_ctrl(ctrl);
imgui.set_key_alt(alt);
imgui.set_key_shift(shift);
imgui.set_key_super(super_);
}

match *event {
Event::MouseWheel { y, .. } => {
imgui.set_mouse_wheel(y as f32);
}
Event::MouseButtonDown { mouse_btn, .. } => {
if mouse_btn != MouseButton::Unknown {
let index = match mouse_btn {
MouseButton::Left => 0,
MouseButton::Right => 1,
MouseButton::Middle => 2,
MouseButton::X1 => 3,
MouseButton::X2 => 4,
MouseButton::Unknown => unreachable!(),
};
self.mouse_press[index] = true;
}
}
Event::TextInput { ref text, .. } => {
for chr in text.chars() {
imgui.add_input_character(chr);
}
}
Event::KeyDown {
scancode, keymod, ..
} => {
set_mod(imgui, keymod);
if let Some(scancode) = scancode {
imgui.set_key(scancode as u8, true);
}
}
Event::KeyUp {
scancode, keymod, ..
} => {
set_mod(imgui, keymod);
if let Some(scancode) = scancode {
imgui.set_key(scancode as u8, false);
}
}
_ => {}
}
}

pub fn frame<'ui>(
&mut self,
window: &Window,
imgui: &'ui mut ImGui,
mouse_state: &MouseState,
) -> imgui::Ui<'ui> {
let mouse_util = window.subsystem().sdl().mouse();

// Merging the mousedown events we received into the current state prevents us from missing
// clicks that happen faster than a frame
let mouse_down = [
self.mouse_press[0] || mouse_state.left(),
self.mouse_press[1] || mouse_state.right(),
self.mouse_press[2] || mouse_state.middle(),
self.mouse_press[3] || mouse_state.x1(),
self.mouse_press[4] || mouse_state.x2(),
];
imgui.set_mouse_down(mouse_down);
self.mouse_press = [false; 5];

let any_mouse_down = mouse_down.iter().any(|&b| b);
mouse_util.capture(any_mouse_down);

imgui.set_mouse_pos(mouse_state.x() as f32, mouse_state.y() as f32);

let mouse_cursor = imgui.mouse_cursor();
if imgui.mouse_draw_cursor() || mouse_cursor == ImGuiMouseCursor::None {
self.cursor = (ImGuiMouseCursor::None, None);
mouse_util.show_cursor(false);
} else {
mouse_util.show_cursor(true);

if mouse_cursor != self.cursor.0 {
let sdl_cursor = match mouse_cursor {
ImGuiMouseCursor::None => unreachable!("mouse_cursor was None!"),
ImGuiMouseCursor::Arrow => SystemCursor::Arrow,
ImGuiMouseCursor::TextInput => SystemCursor::IBeam,
ImGuiMouseCursor::ResizeAll => SystemCursor::SizeAll,
ImGuiMouseCursor::ResizeNS => SystemCursor::SizeNS,
ImGuiMouseCursor::ResizeEW => SystemCursor::SizeWE,
ImGuiMouseCursor::ResizeNESW => SystemCursor::SizeNESW,
ImGuiMouseCursor::ResizeNWSE => SystemCursor::SizeNWSE,
ImGuiMouseCursor::Hand => SystemCursor::Hand,
};

let sdl_cursor = Cursor::from_system(sdl_cursor).unwrap();
sdl_cursor.set();

self.cursor = (mouse_cursor, Some(sdl_cursor));
}
}

let now = Instant::now();
let delta = now - self.last_frame;
let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0;
self.last_frame = now;

let window_size = window.size();
let display_size = window.drawable_size();

let frame_size = imgui::FrameSize {
logical_size: (window_size.0 as f64, window_size.1 as f64),
hidpi_factor: (display_size.0 as f64) / (window_size.0 as f64),
};
let ui = imgui.frame(frame_size, delta_s);

self.ignore_keyboard = ui.want_capture_keyboard();
self.ignore_mouse = ui.want_capture_mouse();

ui
}
}

#[doc(hidden)]
pub extern "C" fn get_clipboard_text(_user_data: *mut c_void) -> *const c_char {
unsafe { sdl2_sys::SDL_GetClipboardText() }
}

#[doc(hidden)]
#[cfg_attr(feature = "cargo-clippy", allow(not_unsafe_ptr_arg_deref))]
pub extern "C" fn set_clipboard_text(_user_data: *mut c_void, text: *const c_char) {
unsafe { sdl2_sys::SDL_SetClipboardText(text) };
}

+ 211
- 0
src/main.rs View File

@@ -0,0 +1,211 @@
// Copyright (C) 2019 Inderjit Gill

// 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 3 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, see <https://www.gnu.org/licenses/>.

// Order of mod matters!. Declare gl_util before using it in other modules
#[macro_use]
mod gl_util;
mod matrix_util;

mod error;
mod input_imgui;
mod render_gl;
mod render_imgui;
mod render_square;

use std::path::Path;

use clap::{value_t, App, Arg};
use config;
use env_logger;
use gl;
use imgui;
use imgui::im_str;
use log::info;
use sdl2;

use crate::error::Result;

fn main() -> Result<()> {
// Add in `./Config.toml`
// Add in config from the environment (with a prefix of DEPICT)
// Eg.. `DEPICT_DEBUG=1 ./target/app` would set the `debug` key
//
let mut config = config::Config::default();
config
.merge(config::File::with_name("Config"))?
.merge(config::Environment::with_prefix("DEPICT"))?;

// update config with command line options
//
let matches = App::new("seni-gui")
.version("4.1.0")
.author("Inderjit Gill <email@indy.io>")
.about("A tool to play with GLSL Shaders")
.arg(
Arg::with_name("SCRIPT")
.help("Sets the input seni script to use")
.index(1),
)
.arg(
Arg::with_name("seed")
.short("s")
.long("seed")
.help("The seed to use")
.takes_value(true),
)
.get_matches();

env_logger::init();

if let Some(script) = matches.value_of("SCRIPT") {
// this should always pass as SCRIPT is required
info!("Using script file: {}", script);

config.set("script", script)?;
}

if let Ok(seed) = value_t!(matches.value_of("seed"), i64) {
config.set("seed", seed)?;
}

run(&config)
}

fn run(config: &config::Config) -> Result<()> {
let assets_location = config.get_str("assets_path")?;
let assets_path = Path::new(&assets_location);

let bitmaps_location = config.get_str("bitmaps_path")?;
let bitmaps_path = Path::new(&bitmaps_location);

let script_filename = config.get_str("script")?;

let sdl_context = sdl2::init()?;
let video = sdl_context.video()?;

{
let gl_attr = video.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(4, 5);
}

let window = video
.window("depict", 1000, 1000)
.position_centered()
.resizable()
.opengl()
.allow_highdpi()
.build()?;

let _gl_context = window
.gl_create_context()
.expect("Couldn't create GL context");
// provide a function to load function pointer by string
let gl = gl::Gl::load_with(|s| video.gl_get_proc_address(s) as _);

let mut imgui = imgui::ImGui::init();
imgui.set_ini_filename(None);

let mut input_imgui = input_imgui::ImguiSdl2::new(&mut imgui);

let mut viewport_width: usize = 900;
let mut viewport_height: usize = 700;

unsafe {
// set up shared state for window
//
gl.ClearColor(1.0, 1.0, 1.0, 1.0);

// assuming that we'll be using pre-multiplied alpha
// see http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
gl.Enable(gl::BLEND);
gl.BlendEquation(gl::FUNC_ADD);
gl.BlendFunc(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
}

let imgui_renderer = render_imgui::Renderer::new(&gl, &assets_path, &mut imgui)?;
let square_renderer = render_square::Renderer::new(&gl, &assets_path)?;

gl_util::update_viewport(&gl, viewport_width, viewport_height);

let mut event_pump = sdl_context.event_pump()?;

'running: loop {
use sdl2::event::{Event, WindowEvent};
use sdl2::keyboard::Keycode;

for event in event_pump.poll_iter() {
input_imgui.handle_event(&mut imgui, &event);
if input_imgui.ignore_event(&event) {
continue;
}

match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => break 'running,
Event::Window {
win_event: WindowEvent::Resized(w, h),
..
} => {
viewport_width = w as _;
viewport_height = h as _;
gl_util::update_viewport(&gl, viewport_width, viewport_height);
}
_ => {}
}
}

let ui = input_imgui.frame(&window, &mut imgui, &event_pump.mouse_state());
ui.show_demo_window(&mut true);

// ~/repos/rust/imgui-rs/imgui-glium-examples/examples/test_window_impl.rs
// ui.window(im_str!("Seni"))
// .position((0.0, 0.0), imgui::ImGuiCond::FirstUseEver)
// .size((800.0, 600.0), imgui::ImGuiCond::FirstUseEver)
// .build(|| {
// if ui.button(im_str!("Load.."), (0.0, 0.0)) {
// ui.open_popup(im_str!("modal"));
// }

// ui.popup_modal(im_str!("modal")).build(|| {
// ui.text("Content of my modal");
// if ui.button(im_str!("OK"), (0.0, 0.0)) {
// ui.close_current_popup();
// }
// });

// ui.separator();
// if ui.collapsing_header(im_str!("script")).build() {
// ui.text(im_str!("{}", seni_source));
// }
// });

unsafe {
gl.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
}

square_renderer.render(viewport_width, viewport_height);
imgui_renderer.render(ui);

window.gl_swap_window();

::std::thread::sleep(::std::time::Duration::new(0, 1_000_000_000u32 / 60));
}

Ok(())
}

+ 67
- 0
src/matrix_util.rs View File

@@ -0,0 +1,67 @@
// Copyright (C) 2019 Inderjit Gill

// 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 3 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, see <https://www.gnu.org/licenses/>.

pub fn create_identity_matrix() -> [f32; 16] {
let out: [f32; 16] = [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];
out
}

pub fn create_ortho_matrix(
left: f32,
right: f32,
bottom: f32,
top: f32,
near: f32,
far: f32,
) -> [f32; 16] {
let lr = 1.0 / (left - right);
let bt = 1.0 / (bottom - top);
let nf = 1.0 / (near - far);

let out: [f32; 16] = [
-2.0 * lr,
0.0,
0.0,
0.0,
0.0,
-2.0 * bt,
0.0,
0.0,
0.0,
0.0,
2.0 * nf,
0.0,
(left + right) * lr,
(top + bottom) * bt,
(far + near) * nf,
1.0,
];

out
}

pub fn matrix_scale(mat: &mut [f32; 16], x: f32, y: f32, z: f32) {
mat[0] *= x;
mat[5] *= y;
mat[10] *= z;
}

pub fn matrix_translate(mat: &mut [f32; 16], x: f32, y: f32, z: f32) {
mat[12] += x;
mat[13] += y;
mat[14] += z;
}

+ 214
- 0
src/render_gl.rs View File

@@ -0,0 +1,214 @@
use std::ffi;
use std::fs;
use std::io::Read;
use std::path::Path;

use gl;
use std;
use std::ffi::{CStr, CString};

use crate::error::{Error, Result};

pub struct Program {
gl: gl::Gl,
id: gl::types::GLuint,
}

impl Program {
pub fn from_path(gl: &gl::Gl, path: &Path, name: &str) -> Result<Program> {
const POSSIBLE_EXT: [&str; 2] = [".vert", ".frag"];

let resource_names = POSSIBLE_EXT
.iter()
.map(|file_extension| format!("{}{}", name, file_extension))
.collect::<Vec<String>>();

let shaders = resource_names
.iter()
.map(|resource_name| Shader::from_path(gl, path, resource_name))
.collect::<Result<Vec<Shader>>>()?;

Program::from_shaders(gl, &shaders[..]).map_err(|message| Error::LinkError {
name: name.into(),
message: message.to_string(),
})
}

pub fn from_shaders(gl: &gl::Gl, shaders: &[Shader]) -> Result<Program> {
let program_id = unsafe { gl.CreateProgram() };

for shader in shaders {
unsafe {
gl.AttachShader(program_id, shader.id());
}
}

unsafe {
gl.LinkProgram(program_id);
}

let mut success: gl::types::GLint = 1;
unsafe {
gl.GetProgramiv(program_id, gl::LINK_STATUS, &mut success);
}

if success == 0 {
let mut len: gl::types::GLint = 0;
unsafe {
gl.GetProgramiv(program_id, gl::INFO_LOG_LENGTH, &mut len);
}

let error = create_whitespace_cstring_with_len(len as usize);

unsafe {
gl.GetProgramInfoLog(
program_id,
len,
std::ptr::null_mut(),
error.as_ptr() as *mut gl::types::GLchar,
);
}

return Err(Error::Placeholder(error.to_string_lossy().into_owned()));
}

for shader in shaders {
unsafe {
gl.DetachShader(program_id, shader.id());
}
}

Ok(Program {
gl: gl.clone(),
id: program_id,
})
}

pub fn id(&self) -> gl::types::GLuint {
self.id
}

pub fn set_used(&self) {
unsafe {
self.gl.UseProgram(self.id);
}
}
}

impl Drop for Program {
fn drop(&mut self) {
unsafe {
self.gl.DeleteProgram(self.id);
}
}
}

pub struct Shader {
gl: gl::Gl,
id: gl::types::GLuint,
}

impl Shader {
pub fn from_path(gl: &gl::Gl, path: &Path, name: &str) -> Result<Shader> {
const POSSIBLE_EXT: [(&str, gl::types::GLenum); 2] =
[(".vert", gl::VERTEX_SHADER), (".frag", gl::FRAGMENT_SHADER)];

let shader_kind = POSSIBLE_EXT
.iter()
.find(|&&(file_extension, _)| name.ends_with(file_extension))
.map(|&(_, kind)| kind)
.ok_or_else(|| Error::CanNotDetermineShaderType { name: name.into() })?;

let source = load_cstring(path, name).map_err(|_e| Error::AssetLoad {
name: name.into(),
// inner: e,
})?;

Shader::from_source(gl, &source, shader_kind).map_err(|message| Error::CompileError {
name: name.into(),
message: message.to_string(),
})
}

pub fn from_source(gl: &gl::Gl, source: &CStr, kind: gl::types::GLenum) -> Result<Shader> {
let id = shader_from_source(gl, source, kind)?;
Ok(Shader { gl: gl.clone(), id })
}

pub fn id(&self) -> gl::types::GLuint {
self.id
}
}

impl Drop for Shader {
fn drop(&mut self) {
unsafe {
self.gl.DeleteShader(self.id);
}
}
}

fn shader_from_source(
gl: &gl::Gl,
source: &CStr,
kind: gl::types::GLenum,
) -> Result<gl::types::GLuint> {
let id = unsafe { gl.CreateShader(kind) };
unsafe {
gl.ShaderSource(id, 1, &source.as_ptr(), std::ptr::null());
gl.CompileShader(id);
}

let mut success: gl::types::GLint = 1;
unsafe {
gl.GetShaderiv(id, gl::COMPILE_STATUS, &mut success);
}

if success == 0 {
let mut len: gl::types::GLint = 0;
unsafe {
gl.GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut len);
}

let error = create_whitespace_cstring_with_len(len as usize);

unsafe {
gl.GetShaderInfoLog(
id,
len,
std::ptr::null_mut(),
error.as_ptr() as *mut gl::types::GLchar,
);
}

return Err(Error::Placeholder(error.to_string_lossy().into_owned()));
}

Ok(id)
}

// todo: move these into gl_util or replace them

fn create_whitespace_cstring_with_len(len: usize) -> CString {
// allocate buffer of correct size
let mut buffer: Vec<u8> = Vec::with_capacity(len + 1);
// fill it with len spaces
buffer.extend([b' '].iter().cycle().take(len));
// convert buffer to CString
unsafe { CString::from_vec_unchecked(buffer) }
}

fn load_cstring(path: &Path, resource_name: &str) -> Result<ffi::CString> {
let mut file = fs::File::open(path.join(resource_name))?;

// allocate buffer of the same size as file
let mut buffer: Vec<u8> = Vec::with_capacity(file.metadata()?.len() as usize + 1);
file.read_to_end(&mut buffer)?;

// check for nul byte
if buffer.iter().find(|i| **i == 0).is_some() {
return Err(Error::FileContainsNil);
}

Ok(unsafe { ffi::CString::from_vec_unchecked(buffer) })
}

+ 314
- 0
src/render_imgui.rs View File

@@ -0,0 +1,314 @@
// code from:
// https://github.com/michaelfairley/rust-imgui-opengl-renderer.git
// licensed under MIT/Apache 2.0
//
use std::mem;
use std::path::Path;

use gl::types::*;
use imgui::{ImGui, Ui};

use crate::error::Result;
use crate::gl_util::return_param;
use crate::render_gl;

pub struct Renderer {
gl: gl::Gl,
program: render_gl::Program,
locs: Locs,
vbo: GLuint,
ebo: GLuint,
font_texture: GLuint,
}

struct Locs {
texture: GLint,
proj_mtx: GLint,
position: GLuint,
uv: GLuint,
color: GLuint,
}

impl Renderer {
pub fn new(gl: &gl::Gl, assets_path: &Path, imgui: &mut ImGui) -> Result<Renderer> {
let gl = gl.clone();

let program = render_gl::Program::from_path(&gl, assets_path, "shaders/imgui")?;

unsafe {
let locs = Locs {
texture: gl.GetUniformLocation(program.id(), c_str!("Texture").as_ptr() as _),
proj_mtx: gl.GetUniformLocation(program.id(), c_str!("ProjMtx").as_ptr() as _),
position: gl.GetAttribLocation(program.id(), c_str!("Position").as_ptr() as _) as _,
uv: gl.GetAttribLocation(program.id(), c_str!("UV").as_ptr() as _) as _,
color: gl.GetAttribLocation(program.id(), c_str!("Color").as_ptr() as _) as _,
};

let vbo = return_param(|x| gl.GenBuffers(1, x));
let ebo = return_param(|x| gl.GenBuffers(1, x));

let mut current_texture = 0;
gl.GetIntegerv(gl::TEXTURE_BINDING_2D, &mut current_texture);

let font_texture = return_param(|x| gl.GenTextures(1, x));
gl.BindTexture(gl::TEXTURE_2D, font_texture);
gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
gl.PixelStorei(gl::UNPACK_ROW_LENGTH, 0);

imgui.prepare_texture(|handle| {
gl.TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as _,
handle.width as _,
handle.height as _,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
handle.pixels.as_ptr() as _,
);
});

gl.BindTexture(gl::TEXTURE_2D, current_texture as _);

imgui.set_font_texture_id((font_texture as usize).into());

Ok(Self {
gl,
program,
locs,
vbo,
ebo,
font_texture,
})
}
}

pub fn render<'ui>(&self, ui: Ui<'ui>) {
use imgui::{ImDrawIdx, ImDrawVert};

let gl = &self.gl;

unsafe {
let last_active_texture = return_param(|x| gl.GetIntegerv(gl::ACTIVE_TEXTURE, x));
gl.ActiveTexture(gl::TEXTURE0);
let last_program = return_param(|x| gl.GetIntegerv(gl::CURRENT_PROGRAM, x));
let last_texture = return_param(|x| gl.GetIntegerv(gl::TEXTURE_BINDING_2D, x));
let last_sampler = if gl.BindSampler.is_loaded() {
return_param(|x| gl.GetIntegerv(gl::SAMPLER_BINDING, x))
} else {
0
};
let last_array_buffer = return_param(|x| gl.GetIntegerv(gl::ARRAY_BUFFER_BINDING, x));
let last_element_array_buffer =
return_param(|x| gl.GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, x));
let last_vertex_array = return_param(|x| gl.GetIntegerv(gl::VERTEX_ARRAY_BINDING, x));
let last_polygon_mode =
return_param(|x: &mut [GLint; 2]| gl.GetIntegerv(gl::POLYGON_MODE, x.as_mut_ptr()));
let last_viewport =
return_param(|x: &mut [GLint; 4]| gl.GetIntegerv(gl::VIEWPORT, x.as_mut_ptr()));
let last_scissor_box =
return_param(|x: &mut [GLint; 4]| gl.GetIntegerv(gl::SCISSOR_BOX, x.as_mut_ptr()));
let last_blend_src_rgb = return_param(|x| gl.GetIntegerv(gl::BLEND_SRC_RGB, x));
let last_blend_dst_rgb = return_param(|x| gl.GetIntegerv(gl::BLEND_DST_RGB, x));
let last_blend_src_alpha = return_param(|x| gl.GetIntegerv(gl::BLEND_SRC_ALPHA, x));
let last_blend_dst_alpha = return_param(|x| gl.GetIntegerv(gl::BLEND_DST_ALPHA, x));
let last_blend_equation_rgb =
return_param(|x| gl.GetIntegerv(gl::BLEND_EQUATION_RGB, x));
let last_blend_equation_alpha =
return_param(|x| gl.GetIntegerv(gl::BLEND_EQUATION_ALPHA, x));
let last_enable_blend = gl.IsEnabled(gl::BLEND) == gl::TRUE;
let last_enable_cull_face = gl.IsEnabled(gl::CULL_FACE) == gl::TRUE;
let last_enable_depth_test = gl.IsEnabled(gl::DEPTH_TEST) == gl::TRUE;
let last_enable_scissor_test = gl.IsEnabled(gl::SCISSOR_TEST) == gl::TRUE;

gl.Enable(gl::BLEND);
gl.BlendEquation(gl::FUNC_ADD);
gl.BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl.Disable(gl::CULL_FACE);
gl.Disable(gl::DEPTH_TEST);
gl.Enable(gl::SCISSOR_TEST);
gl.PolygonMode(gl::FRONT_AND_BACK, gl::FILL);

let (width, height) = ui.imgui().display_size();

let fb_width = width * ui.imgui().display_framebuffer_scale().0;
let fb_height = height * ui.imgui().display_framebuffer_scale().1;

gl.Viewport(0, 0, fb_width as _, fb_height as _);
let matrix = [
[2.0 / width as f32, 0.0, 0.0, 0.0],
[0.0, 2.0 / -(height as f32), 0.0, 0.0],
[0.0, 0.0, -1.0, 0.0],
[-1.0, 1.0, 0.0, 1.0],
];
gl.UseProgram(self.program.id());
gl.Uniform1i(self.locs.texture, 0);
gl.UniformMatrix4fv(self.locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
if gl.BindSampler.is_loaded() {
gl.BindSampler(0, 0);
}

let vao = return_param(|x| gl.GenVertexArrays(1, x));
gl.BindVertexArray(vao);
gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo);
gl.EnableVertexAttribArray(self.locs.position);
gl.EnableVertexAttribArray(self.locs.uv);
gl.EnableVertexAttribArray(self.locs.color);
gl.VertexAttribPointer(
self.locs.position,
2,
gl::FLOAT,
gl::FALSE,
mem::size_of::<ImDrawVert>() as _,
field_offset::<ImDrawVert, _, _>(|v| &v.pos) as _,
);
gl.VertexAttribPointer(
self.locs.uv,
2,
gl::FLOAT,
gl::FALSE,
mem::size_of::<ImDrawVert>() as _,
field_offset::<ImDrawVert, _, _>(|v| &v.uv) as _,
);
gl.VertexAttribPointer(
self.locs.color,
4,
gl::UNSIGNED_BYTE,
gl::TRUE,
mem::size_of::<ImDrawVert>() as _,
field_offset::<ImDrawVert, _, _>(|v| &v.col) as _,
);

ui.render::<_, ()>(|ui, mut draw_data| {
draw_data.scale_clip_rects(ui.imgui().display_framebuffer_scale());

for draw_list in &draw_data {
gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo);
gl.BufferData(
gl::ARRAY_BUFFER,
(draw_list.vtx_buffer.len() * mem::size_of::<ImDrawVert>()) as _,
draw_list.vtx_buffer.as_ptr() as _,
gl::STREAM_DRAW,
);

gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
gl.BufferData(
gl::ELEMENT_ARRAY_BUFFER,
(draw_list.idx_buffer.len() * mem::size_of::<ImDrawIdx>()) as _,
draw_list.idx_buffer.as_ptr() as _,
gl::STREAM_DRAW,
);

let mut idx_start = 0;
for cmd in draw_list.cmd_buffer {
if let Some(_callback) = cmd.user_callback {
unimplemented!("Haven't implemented user callbacks yet");
} else {
gl.BindTexture(gl::TEXTURE_2D, cmd.texture_id as _);
gl.Scissor(
cmd.clip_rect.x as GLint,
(fb_height - cmd.clip_rect.w) as GLint,
(cmd.clip_rect.z - cmd.clip_rect.x) as GLint,
(cmd.clip_rect.w - cmd.clip_rect.y) as GLint,
);
gl.DrawElements(
gl::TRIANGLES,
cmd.elem_count as _,
if mem::size_of::<ImDrawIdx>() == 2 {
gl::UNSIGNED_SHORT
} else {
gl::UNSIGNED_INT
},
idx_start as _,
);
}
idx_start += cmd.elem_count * mem::size_of::<ImDrawIdx>() as u32;
}
}

Ok(())
})
.unwrap();
gl.DeleteVertexArrays(1, &vao);

gl.UseProgram(last_program as _);
gl.BindTexture(gl::TEXTURE_2D, last_texture as _);
if gl.BindSampler.is_loaded() {
gl.BindSampler(0, last_sampler as _);
}
gl.ActiveTexture(last_active_texture as _);
gl.BindVertexArray(last_vertex_array as _);
gl.BindBuffer(gl::ARRAY_BUFFER, last_array_buffer as _);
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, last_element_array_buffer as _);
gl.BlendEquationSeparate(last_blend_equation_rgb as _, last_blend_equation_alpha as _);
gl.BlendFuncSeparate(
last_blend_src_rgb as _,
last_blend_dst_rgb as _,
last_blend_src_alpha as _,
last_blend_dst_alpha as _,
);
if last_enable_blend {
gl.Enable(gl::BLEND)
} else {
gl.Disable(gl::BLEND)
};
if last_enable_cull_face {
gl.Enable(gl::CULL_FACE)
} else {
gl.Disable(gl::CULL_FACE)
};
if last_enable_depth_test {
gl.Enable(gl::DEPTH_TEST)
} else {
gl.Disable(gl::DEPTH_TEST)
};
if last_enable_scissor_test {
gl.Enable(gl::SCISSOR_TEST)
} else {
gl.Disable(gl::SCISSOR_TEST)
};
gl.PolygonMode(gl::FRONT_AND_BACK, last_polygon_mode[0] as _);
gl.Viewport(
last_viewport[0] as _,
last_viewport[1] as _,
last_viewport[2] as _,
last_viewport[3] as _,
);
gl.Scissor(
last_scissor_box[0] as _,
last_scissor_box[1] as _,
last_scissor_box[2] as _,
last_scissor_box[3] as _,
);
}
}
}

impl Drop for Renderer {
fn drop(&mut self) {
let gl = &self.gl;

unsafe {
gl.DeleteBuffers(1, &self.vbo);
gl.DeleteBuffers(1, &self.ebo);
gl.DeleteTextures(1, &self.font_texture);
}
}
}

fn field_offset<T, U, F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> usize {
unsafe {
let instance = mem::uninitialized::<T>();

let offset = {
let field: &U = f(&instance);
field as *const U as usize - &instance as *const T as usize
};

mem::forget(instance);

offset
}
}

+ 234
- 0
src/render_square.rs View File

@@ -0,0 +1,234 @@
// Copyright (C) 2019 Inderjit Gill

// 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 3 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, see <https://www.gnu.org/licenses/>.

use gl::types::*;

use std::path::Path;

use crate::error::Result;
use crate::matrix_util;
use crate::render_gl;

struct GeometryLayout {
stride: usize,
position_num_elements: usize,
texture_num_elements: usize,
}

pub struct Renderer {
gl: gl::Gl,
program: render_gl::Program,

vao: GLuint,
vbo: GLuint,

texture: GLuint,

geometry: Vec<f32>,

locations: RendererLocations,
}

struct RendererLocations {
texture: GLint,
projection_mtx: GLint,
modelview_mtx: GLint,

position: GLuint,
uv: GLuint,
}

impl Drop for Renderer {
fn drop(&mut self) {
let gl = &self.gl;

// todo: should program be explicitly dropped or does that happen implicitly?
unsafe {
gl.DeleteBuffers(1, &self.vbo);
gl.DeleteTextures(1, &self.texture);
gl.DeleteVertexArrays(1, &self.vao);
}
}
}

impl Renderer {
pub fn new(gl: &gl::Gl, assets_path: &Path) -> Result<Renderer> {
let program = render_gl::Program::from_path(gl, assets_path, "shaders/blit")?;

let mut vao: gl::types::GLuint = 0;
let mut vbo: gl::types::GLuint = 0;

let location_texture: gl::types::GLint;
let location_projection_mtx: gl::types::GLint;
let location_modelview_mtx: gl::types::GLint;
let location_position: gl::types::GLuint;
let location_uv: gl::types::GLuint;

program.set_used();

unsafe {
// set up vertex array object
//
gl.GenVertexArrays(1, &mut vao);
gl.BindVertexArray(vao);

// set up vertex buffer object
//
gl.GenBuffers(1, &mut vbo);
gl.BindBuffer(gl::ARRAY_BUFFER, vbo);

location_texture = 0;
// location_texture =
// gl.GetUniformLocation(program.id(), c_str!("myTextureSampler").as_ptr());
location_projection_mtx =
gl.GetUniformLocation(program.id(), c_str!("uPMatrix").as_ptr());
location_modelview_mtx =
gl.GetUniformLocation(program.id(), c_str!("uMVMatrix").as_ptr());

location_position =
gl.GetAttribLocation(program.id(), c_str!("Position").as_ptr()) as _;
location_uv = gl.GetAttribLocation(program.id(), c_str!("UV").as_ptr()) as _;

// gl.BindTexture(gl::TEXTURE_2D, texture);

// gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
// gl.TexParameteri(
// gl::TEXTURE_2D,
// gl::TEXTURE_MIN_FILTER,
// gl::LINEAR_MIPMAP_LINEAR as i32,
// );
// gl.GenerateMipmap(gl::TEXTURE_2D);
}

let locations = RendererLocations {
texture: location_texture,
projection_mtx: location_projection_mtx,
modelview_mtx: location_modelview_mtx,
position: location_position,
uv: location_uv,
};

let projection_matrix =
matrix_util::create_ortho_matrix(0.0, 1000.0, 0.0, 1000.0, 10.0, -10.0);
let mut model_view_matrix = matrix_util::create_identity_matrix();
matrix_util::matrix_scale(&mut model_view_matrix, 800.0, 800.0, 1.0);
matrix_util::matrix_translate(&mut model_view_matrix, 100.0, 100.0, 0.0);

let layout = GeometryLayout {
stride: 4, // x, y, u, v
position_num_elements: 2,
texture_num_elements: 2,
};

unsafe {
gl.Uniform1i(locations.texture, 0);
gl.UniformMatrix4fv(
locations.projection_mtx,
1,
gl::FALSE,
projection_matrix.as_ptr(),
);
gl.UniformMatrix4fv(
locations.modelview_mtx,
1,
gl::FALSE,
model_view_matrix.as_ptr(),
);

gl.EnableVertexAttribArray(locations.position);
gl.EnableVertexAttribArray(locations.uv);

gl.VertexAttribPointer(
locations.position,
layout.position_num_elements as i32, // the number of components per generic vertex attribute
gl::FLOAT, // data type
gl::FALSE, // normalized (int-to-float conversion)
(layout.stride * std::mem::size_of::<f32>()) as gl::types::GLint, // stride
std::ptr::null(), // offset of the first component
);

let texture_offset = layout.position_num_elements;
gl.VertexAttribPointer(
locations.uv,
layout.texture_num_elements as i32, // the number of components per generic vertex attribute
gl::FLOAT, // data type
gl::FALSE, // normalized (int-to-float conversion)
(layout.stride * std::mem::size_of::<f32>()) as gl::types::GLint, // stride
(texture_offset * std::mem::size_of::<f32>()) as *const gl::types::GLvoid, // offset of the first component
);
}

// generate geometry
let geometry: Vec<f32> = vec![
0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0,
];

unsafe {
gl.BufferData(
gl::ARRAY_BUFFER, // target
(geometry.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
geometry.as_ptr() as *const gl::types::GLvoid, // pointer to data
gl::STATIC_DRAW, // usage
);
}

let texture: GLuint = 0;

Ok(Renderer {
gl: gl.clone(),
program,
vao,
vbo,
texture,
geometry,
locations,
})
}

pub fn render(&self, viewport_width: usize, viewport_height: usize) {
let gl = &self.gl;

let projection_matrix = matrix_util::create_ortho_matrix(
0.0,
viewport_width as f32,
0.0,
viewport_height as f32,
10.0,
-10.0,
);

unsafe {
gl.ActiveTexture(gl::TEXTURE0);
gl.BindTexture(gl::TEXTURE_2D, self.texture);

gl.UseProgram(self.program.id());

gl.BindVertexArray(self.vao);

gl.UniformMatrix4fv(
self.locations.projection_mtx,
1,
gl::FALSE,
projection_matrix.as_ptr(),
);

gl.DrawArrays(
gl::TRIANGLE_STRIP, // mode
0, // starting index in the enabled arrays
self.geometry.len() as i32, // number of indices to be rendered
);
}
}
}

Loading…
Cancel
Save