My origin of this approach comes from OpenGL shader programming, but the problem is more abstract. I will write some pseudo code to clarify what I mean.
In OpenGL, rendering is done in so called "shaders". A shader is a calculation kernel that is applied to each element of a data set, but with the advantage that the calculation is executed on the GPU and therefore takes the advantage of concurrent nature of the GPU to calculate as much at the same time as possible.
The problem is that shaders are presented as text at compile time, and the shader needs to be compiled at runtime by the driver of the GPU. This means that at the start of each program an init function needs to compile each shader source file into a program before the shader can be called. Here is an example, keep in mind it is simplified pseudocode:
let shader_src_A = r#"
attribute float a;
attribute float b;
out float b;
void main() {
b = a * b;
}
"#;
let shader_src_B = r#"
attribute float a;
attribute float b;
out float b;
void main() {
b = a + b;
}
"#;
let mut program_A : ShaderProgram;
let mut program_B : ShaderProgram;
fn init() {
initGL();
program_A = compile_and_link(shader_src_A);
program_B = compile_and_link(shader_src_B);
}
fn render() {
let data1 = vec![1,2,3,4];
let data2 = vec![5,6,7,8];
// move data to the gpu
let gpu_data_1 = move_to_gpu(data1);
let gpu_data_2 = move_to_gpu(data2);
let gpu_data_3 : GpuData<float>;
let gpu_data_4 : GpuData<float>;
program_A(
(gpu_data_1, gpu_data_2) // input
(gpu_data_3,) // output
);
program_B(
(gpu_data_1, gpu_data_2) // input
(gpu_data_4,) // output
);
let data_3 = move_to_cpu(gpu_data_3);
let data_4 = move_to_cpu(gpu_data_4);
println!("data_3 {:?} data_4 {:?}", data_3, data_4);
// data_3 [5, 12, 21, 32] data_4 [6, 8, 10, 12]
}
The goal for me to to be able to write something like this:
fn init() {
initGL();
mystery_macro!();
}
fn render() {
let data1 = vec![1,2,3,4];
let data2 = vec![5,6,7,8];
// move data to the gpu
let gpu_data_1 = move_to_gpu(data1);
let gpu_data_2 = move_to_gpu(data2);
let gpu_data_3 : GpuData<float>;
let gpu_data_4 : GpuData<float>;
shade!(
(gpu_data_1, gpu_data_2), // input tuple
(gpu_data_3,), // output tuple
"gpu_data_3 = gpu_data_1 * gpu_data_2;" // this is the shader source, the rest should be generated by the macro.
);
shade!(
(gpu_data_1, gpu_data_2), // input tuple
(gpu_data_3,), // output tuple
"gpu_data_4 = gpu_data_1 + gpu_data_2;" // this is the shader source, the rest should be generated by the macro.
);
let data_3 = move_to_cpu(gpu_data_3);
let data_4 = move_to_cpu(gpu_data_4);
println!("data_3 {:?} data_4 {:?}", data_3, data_4);
}
The key difference is that I do not have a common place where all my shaders are written. I write my shaders where I call them, and I do not write the part of the shader that can be inferred by the other arguments. Generating the part of the shader that is missing should be straight forward, the problem is the compilation of the shader. A renderer that calls the compilation of each shader on each call is far too slow to be useful at all. The idea is that the macro should generate this common place with all the shader sources and programs, so that the init function can compile and link all programs at program start.
Despite the title, I am also ok with a solution that solves my problem differently, but I would prefer a solution where all programs can be compiled in the init function.
EDIT:
I could also imagine, that shade is not a macro, but a placeholder no-op function, the macro will then operate on the shade function, and by traversing the AST, it can find all calls to shade, and create everything that needs to be done in the init function.
From The Rust Programming Language section on macros (emphasis mine):
Macros allow us to abstract at a syntactic level. A macro invocation is shorthand for an "expanded" syntactic form. This expansion happens early in compilation, before any static checking. As a result, macros can capture many patterns of code reuse that Rust’s core abstractions cannot.
Said another way, macros are only useful when you already have some code that has appreciable boilerplate. They cannot do something beyond what the code itself does.
Additionally, Rust macros work at a level above C macros. Rust macros are not presented with the raw text, but instead have some pieces of the AST of the program.
Let's start with this simplified version:
struct Shader(usize);
impl Shader {
fn compile(source: &str) -> Shader {
println!("Compiling a shader");
Shader(source.len())
}
fn run(&self) {
println!("Running a shader {}", self.0)
}
}
fn main() {
for _ in 0..10 {
inner_loop();
}
}
fn inner_loop() {
let shader_1_src = r#"add 1 + 1"#;
let shader_1 = Shader::compile(shader_1_src);
let shader_2_src = r#"add 42 + 53"#;
let shader_2 = Shader::compile(shader_2_src);
shader_1.run();
shader_2.run();
}
The biggest problem here is the repeated compilation, so we can lazily compile it once using the lazy_static crate:
#[macro_use]
extern crate lazy_static;
// Previous code...
fn inner_loop() {
const SHADER_1_SRC: &'static str = r#"add 1 + 1"#;
lazy_static! {
static ref SHADER_1: Shader = Shader::compile(SHADER_1_SRC);
}
const SHADER_2_SRC: &'static str = r#"add 42 + 53"#;
lazy_static! {
static ref SHADER_2: Shader = Shader::compile(SHADER_2_SRC);
}
SHADER_1.run();
SHADER_2.run();
}
You can then go one step further and make another macro around that:
// Previous code...
macro_rules! shader {
($src_name: ident, $name: ident, $l: expr, $r: expr) => {
const $src_name: &'static str = concat!("add ", $l, " + ", $r);
lazy_static! {
static ref $name: Shader = Shader::compile($src_name);
}
}
}
fn inner_loop() {
shader!(S1, SHADER_1, "1", "2");
shader!(S2, SHADER_2, "42", "53");
SHADER_1.run();
SHADER_2.run();
}
Note that we have to provide a name for the inner source constant because there's currently no way of generating arbitrary identifiers in macros.
I'm no game programmer, but this type of code would make me wary. At potentially any point, you might execute some shader compilation, slowing down your program. I agree that pre-compiling all your shaders at program startup makes the most sense (or at Rust compile time, if possible!), but it simply doesn't make sense with your desired structure. If you can write plain Rust code that does what you want, then you can make a macro that makes it prettier. I just don't believe it's possible to write Rust code that does what you want.
There is a possibility that a syntax extension may be able to do what you want, but I don't have enough experience with them yet to soundly rule it in or out.
Related
I'm trying to export my OpenGL game from C++ to Rust and achieve 2 things:
1. Print OpenGL error messages to console.
2. Autocompletion of GL functions and constants in my IDE (Visual Studio Code).
I simly generated OpenGL bindings with gl_generator crate and copied bindings.rs file to my cargo.
extern crate sdl2;
mod bindings;
use bindings as GL;
fn main() {
let sdl = sdl2::init().unwrap();
let mut event_pump = sdl.event_pump().unwrap();
let video_subsystem = sdl.video().unwrap();
let gl_attr = video_subsystem.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(4, 5);
let window = video_subsystem.window("Window", 900, 700).opengl().resizable().build().unwrap();
let _gl_context = window.gl_create_context().unwrap();
let gl = GL::Gl::load_with(|s| video_subsystem.gl_get_proc_address(s) as *const std::os::raw::c_void);
unsafe{ gl.Viewport(0, 0, 900, 700); }
'main: loop {
unsafe {
gl.UseProgram(42); // <- error (^ GL error triggered: 1281)
gl.ClearColor(0.0, 0.3, 0.6, 1.0 );
gl.Clear(GL::COLOR_BUFFER_BIT);
}
window.gl_swap_window();
for event in event_pump.poll_iter() {
match event {
sdl2::event::Event::Quit {..} => { break 'main },
_ => {},
}
}
}
}
The problem is that gl variable, where all functions are stored, is not global and I'm not sure how to use it with different modules/functions.
The reason why all functions are inside Gl struct is because I used DebugStructGenerator in my build function. It prints not only errors but all OpenGL function calls (e.g., [OpenGL] ClearColor(0.0, 0.3, 0.6, 1.0)). Would be great if it only printed error messages.
My build.rs file:
extern crate gl_generator;
use gl_generator::{Registry, Fallbacks, /*StructGenerator, GlobalGenerator,*/ DebugStructGenerator, Api, Profile};
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(DebugStructGenerator, &mut file_gl).unwrap();
}
There is no need to include the bindings.rs:
pub mod gl {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
Include this in your main.rs or lib.rs. Then you can include the gl functions from any of your modules just with (as gl is includes as pub module):
// anywhere in a module
use crate::gl; // static gl with lot of constants e.g gl::ARRAY_BUFFER
use crate::gl::Gl; // gl instance struct with gl: &Gl => gl.bindBuffer(...)
use crate::gl::types::*; // include all gl types like GLuint, GLenum, ...
As we are in rust, you need to provide your gl instance to your functions as reference or whatever you want.
Maybe check lazy_static, which can be used as global variable:
lazy_static! {
pub static ref gl: gl::Gl = create_opengl_context();
}
First access to gl will call the create context function. Repeated calls will use the created instance.
Maybe have a look at my game-gl library (graphics branch) at
https://github.com/Kaiser1989/game-gl or the example at https://github.com/Kaiser1989/rust-android-example. (Android and Windows openGL game loop framework)
.
I am working on an off-screen render program and I use crate glium to do this. I have followed the example of screenshot.rs and this example worked well.
Then I made some change:
The orignal code was
fn main() {
// building the display, ie. the main object
let event_loop = glutin::EventsLoop::new();
let wb = glutin::WindowBuilder::new().with_visible(true);
let cb = glutin::ContextBuilder::new();
let display = glium::Display::new(wb, cb, &event_loop).unwrap();
// building the vertex buffer, which contains all the vertices that we will draw
I grouped these codes into a function:
fn main() {
// building the display, ie. the main object
let event_loop = glutin::EventsLoop::new();
let display = build_display((128,128), &event_loop);
// building the vertex buffer, which contains all the vertices that we will draw
pub fn build_display(size: (u32, u32), event_loop: &glutin::EventsLoop) -> glium::Display {
let version = parse_version(); //this will return `OpenGL 3.3`
let wb = glutin::WindowBuilder::new()
.with_visibility(false)
.with_dimensions(glutin::dpi::LogicalSize::from(size));
let cb = glutin::ContextBuilder::new()
.with_gl(version);
glium::Display::new(wb, cb, &event_loop).unwrap()
}
After this modification, the program still worked well. So I continued to add the headless-context:
fn main() {
// building the display, ie. the main object
let event_loop = glutin::EventsLoop::new();
let display = build_display_headless((128,128), &event_loop);
// building the vertex buffer, which contains all the vertices that we will draw
pub fn build_display_headless(size: (u32, u32), event_loop: &glutin::EventsLoop) -> glium::HeadlessRenderer {
let version = parse_version(); // this will return `OpenGL 3.3`
let ctx = glutin::ContextBuilder::new()
.with_gl(version)
.build_headless(&event_loop, glutin::dpi::PhysicalSize::from(size))
.expect("1");
//let ctx = unsafe { ctx.make_current().expect("3") };
glium::HeadlessRenderer::new(ctx).expect("4")
}
But this time, the program did not work. There was no panic during the running, but the output image was empty with black, and its size was not 128x128 but 800x600.
I have tried to remove the libEGL.dll so that, due to the doc of crate glutin, the function .build_headless will build a window and hide it, just as my function build_display does. However, this failed too. So what can cause this?
Disclaimer: I'm totally new to Haxe, but I have experience in many other languages.
I have tests similar to the following:
function doTest(type:SomethingMagic, tests:Array<Array<Int>>) {
for (t in tests) {
var res = DoSomeMagicalWork(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7]);
assertEquals(type, res.type);
}
}
The problem with this is that the unit test framework, when run on many different arrays, doesn't give me the correct line for which the test failed. In other words, if I'm running this method with a bunch of arrays, like:
doTest(SOME_MAGIC_TYPE,
[[0, 0, 0, 1625, 0, 35, 0, 0, 0, 0, 0],
...
]);
and one of these lines fails, it doesn't tell me which line failed. Now, I know that I could probably restructure these tests to be a bit more intuitive anyway, but this was written by someone else, and I don't have the ability to change each of these at the moment.
What I'd like to do is the following:
function doTest(type:SomethingMagic, tests:Array<Array<Int>>) {
var number = 0;
for (t in tests) {
var res = DoSomeMagicalWork(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7]);
assertEquals(type, res.type, "Test #" + number + " for type " + type);
number++;
}
}
So, basically, I'd like to be able to pass in some extra messaging information to the assertEquals function, similar to what one can do in other unit testing frameworks. Then, upon failure, it would output the standard assertion message, possibly appended by the additional message I sent as a parameter to the function. Originally, I thought it was as simple as sub-classing haxe.TestCase, but that doesn't appear to be quite as simple as I thought, due to the way Haxe interprets types (apparently).
Has anyone had success with something similar to this that could give me a recommendation on how to accomplish it?
If you want to only get the position of the error you can use haxe.PosInfos as the last argument of your doTest() function and pass that arguemnt to assertEquals() like this:
import haxe.unit.TestCase;
class Main {
static function main() {
var r = new haxe.unit.TestRunner();
r.add(new Test());
r.run();
}
}
class Test extends TestCase {
public function new() {
super();
}
public function testExample() {
doTest(1, 1);
doTest(1, 2);
doTest(3, 3);
}
function doTest(a:Int, b:Int, ?pos:haxe.PosInfos) {
assertEquals(a, b, pos);
}
}
Online example here
It will give you the position that called doTest() in the error:
Test::testExample() ERR: Main.hx:18(Test.testExample) - expected '1' but was '2'
Another option if you want to add a custom message is to catch the assertEquals() error and rethrow the currentTest with a custom error like this:
import haxe.unit.TestCase;
class Main {
static function main() {
var r = new haxe.unit.TestRunner();
r.add(new Test());
r.run();
}
}
class Test extends TestCase {
public function new() {
super();
}
public function testExample() {
doTest(1, 1, "Error on test 1");
doTest(1, 2, "Error on test 2");
doTest(3, 3, "Error on test 3");
}
function doTest(a:Int, b:Int, errorMsg:String, ?pos:haxe.PosInfos) {
try {
assertEquals(a, b, pos);
} catch(e:Dynamic) {
currentTest.error = errorMsg;
throw currentTest;
}
}
}
Online example here
Which will give you the following error:
Test::testExample() ERR: Main.hx:18(Test.testExample) - Error on test 2
You are effectively blending multiple tests into a single one. And Haxe cannot tell where your array element is defined (line number, etc)
What I suggest is to change the signature of doTest to accept Array<Int> instead of Array<Array<Int>> and call doTest multiple times instead of just once. Together with the suggestion from Justo, passing the pos object to assetEquals, you will get the position correctly.
I'm attempting to implement a simple JIT compiler using the LLVM C API. So far, I have no problems generating IR code and executing it, that is: until I start disposing objects and recreating them.
What I basically would like to do is to clean up the JIT'ted resources the moment they're no longer used by the engine. What I'm basically attempting to do is something like this:
while (true)
{
// Initialize module & builder
InitializeCore(GetGlobalPassRegistry());
module = ModuleCreateWithName(some_unique_name);
builder = CreateBuilder();
// Initialize target & execution engine
InitializeNativeTarget();
engine = CreateExecutionEngineForModule(...);
passmgr = CreateFunctionPassManagerForModule(module);
AddTargetData(GetExecutionEngineTargetData(engine), passmgr);
InitializeFunctionPassManager(passmgr);
// [... my fancy JIT code ...] --** Will give a serious error the second iteration
// Destroy
DisposePassManager(passmgr);
DisposeExecutionEngine(engine);
DisposeBuilder(builder);
// DisposeModule(module); //--> Commented out: Deleted by execution engine
Shutdown();
}
However, this doesn't seem to be working correctly: the second iteration of the loop I get a pretty bad error...
So to summarize: what's the correct way to destroy and re-create the LLVM API?
Posting this as Answer because the code's too long. If possible and no other constraints, try to use LLVM like this. I am pretty sure the Shutdown() inside the loop is the culprit here. And I dont think it would hurt to keep the Builder outside, too. This reflects well the way I use LLVM in my JIT.
InitializeCore(GetGlobalPassRegistry());
InitializeNativeTarget();
builder = CreateBuilder();
while (true)
{
// Initialize module & builder
module = ModuleCreateWithName(some_unique_name);
// Initialize target & execution engine
engine = CreateExecutionEngineForModule(...);
passmgr = CreateFunctionPassManagerForModule(module);
AddTargetData(GetExecutionEngineTargetData(engine), passmgr);
InitializeFunctionPassManager(passmgr);
// [... my fancy JIT code ...] --** Will give a serious error the second iteration
// Destroy
DisposePassManager(passmgr);
DisposeExecutionEngine(engine);
}
DisposeBuilder(builder);
Shutdown();
/* program init */
LLVMInitializeNativeTarget();
LLVMInitializeNativeAsmPrinter();
LLVMInitializeNativeAsmParser();
LLVMLinkInMCJIT();
ctx->context = LLVMContextCreate();
ctx->builder = LLVMCreateBuilderInContext(ctx->context);
LLVMParseBitcodeInContext2(ctx->context, module_template_buf, &module) // create module
do IR code creation
{
function = LLVMAddFunction(ctx->module, "my_func")
LLVMAppendBasicBlockInContext(ctx->context, ...
LLVMBuild...
...
}
optional optimization
{
LLVMPassManagerBuilderRef pass_builder = LLVMPassManagerBuilderCreate();
LLVMPassManagerBuilderSetOptLevel(pass_builder, 3);
LLVMPassManagerBuilderSetSizeLevel(pass_builder, 0);
LLVMPassManagerBuilderUseInlinerWithThreshold(pass_builder, 1000);
LLVMPassManagerRef function_passes = LLVMCreateFunctionPassManagerForModule(ctx->module);
LLVMPassManagerRef module_passes = LLVMCreatePassManager();
LLVMPassManagerBuilderPopulateFunctionPassManager(pass_builder, function_passes);
LLVMPassManagerBuilderPopulateModulePassManager(pass_builder, module_passes);
LLVMPassManagerBuilderDispose(pass_builder);
LLVMInitializeFunctionPassManager(function_passes);
for (LLVMValueRef value = LLVMGetFirstFunction(ctx->module); value;
value = LLVMGetNextFunction(value))
{
LLVMRunFunctionPassManager(function_passes, value);
}
LLVMFinalizeFunctionPassManager(function_passes);
LLVMRunPassManager(module_passes, ctx->module);
LLVMDisposePassManager(function_passes);
LLVMDisposePassManager(module_passes);
}
optional for debug
{
LLVMVerifyModule(ctx->module, LLVMAbortProcessAction, &error);
LLVMPrintModule
}
if (LLVMCreateJITCompilerForModule(&ctx->engine, ctx->module, 0, &error) != 0)
my_func = (exec_func_t)(uintptr_t)LLVMGetFunctionAddress(ctx->engine, "my_func");
LLVMRemoveModule(ctx->engine, ctx->module, &ctx->module, &error);
LLVMDisposeModule(ctx->module);
LLVMDisposeBuilder(ctx->builder);
do
{
my_func(...);
}
LLVMDisposeExecutionEngine(ctx->engine);
LLVMContextDispose(ctx->context);
/* program finit */
LLVMShutdown();
I get the following error when I try to execute the code below.
Uncaught TypeError: Object true has no method 'dartObjectLocalStorage$getter'
I started a Web Application in Dart Editor Version 0.1.0.201201150611 Build 3331. Here is the complete code for the lone dart file. The if statement that results in the error is commented below.
#import('dart:html');
class Test3D {
CanvasElement theCanvas;
WebGLRenderingContext gl;
static String vertexShaderSrc = """
attribute vec3 aVertexPosition;
void main(void) {
gl_Position = vec4(aVertexPosition, 1.0);
}
""";
Test3D() {
}
void run() {
write("Hello World!");
// Set up canvas
theCanvas = new Element.html("<canvas></canvas>");
theCanvas.width = 100;
theCanvas.height = 100;
document.body.nodes.add(theCanvas);
// Set up context
gl = theCanvas.getContext("experimental-webgl");
gl.clearColor(0.5, 0.5, 0.5, 1.0);
gl.clear(WebGLRenderingContext.COLOR_BUFFER_BIT);
WebGLShader vertexShader = gl.createShader(WebGLRenderingContext.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
// Adding this line results in the error: Uncaught TypeError: Object true has no method 'dartObjectLocalStorage$getter
var wasSuccessful = gl.getShaderParameter(vertexShader, WebGLRenderingContext.COMPILE_STATUS);
}
void write(String message) {
// the HTML library defines a global "document" variable
document.query('#status').innerHTML = message;
}
}
void main() {
new Test3D().run();
}
I'm really keen on dart and would appreciate any help you could give me on this.
Here is the console output for the error:
Uncaught TypeError: Object true has no method 'dartObjectLocalStorage$getter'
htmlimpl0a8e4b$LevelDom$Dart.wrapObject$member
htmlimpl0a8e4b$WebGLRenderingContextWrappingImplementation$Dart.getShaderParameter$member
htmlimpl0a8e4b$WebGLRenderingContextWrappingImplementation$Dart.getShaderParameter$named
unnamedb54266$Test3D$Dart.run$member
unnamedb54266$Test3D$Dart.run$named
unnamedb54266$main$member
RunEntry.isolate$current
isolate$Isolate.run
isolate$IsolateEvent.process
isolate$doOneEventLoopIteration
next
isolate$doRunEventLoop
isolate$runEventLoop
RunEntry
(anonymous function)
The error indicates that somewhere in your code the field 'dartObjectLocalStorage' is accessed on the boolean true. The given code-snippet does not contain this identifier and is thus probably not responsible for the error.
It could be that the error-reporting gives the wrong line number (potentially even the wrong file).
To debug this:
try to find a reference to 'dartObjectLocalStorage' in your code.
try to find a reference to 'dartObjectLocalStorage$getter' in the generated code.
run on the VM or compile with a different compiler (frogc vs dartc).
good luck.
According to the documentation for WebGLRenderingContext the return type is Object, if you know the object is bool you can just use it as a bool or dynamic
var v=gl.getShaderParameter(vertexShader, WebGLRenderingContext.COMPILE_STATUS)
or more explicitly
var v=gl.getShaderParameter(vertexShader, WebGLRenderingContext.COMPILE_STATUS).dynamic
and then use it in your conditional statement. Also try compiling it with frogc rather than the build in JavaScript compiler, it usually results in better errors.