wip: restructure

This commit is contained in:
Noah Santschi-Cooney 2022-08-14 12:13:20 +01:00
parent d8d77ac600
commit 786e09bdcf
No known key found for this signature in database
GPG key ID: 3B22282472C8AE48
105 changed files with 5156 additions and 3268 deletions

26
server/opengl/Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "opengl"
version = "0.9.8"
authors = ["Noah Santschi-Cooney <noah@santschi-cooney.ch>"]
edition = "2021"
[lib]
doctest = false
[dependencies]
glutin = "0.28"
gl = "0.14"
url = "2.2"
filesystem = { path = "../filesystem" }
graph = { path = "../graph" }
tower-lsp = "0.17.0"
regex = "1.4"
mockall = "0.11"
logging = { path = "../logging" }
sourcefile = { path = "../sourcefile" }
[dev-dependencies]
# workspace = { path = "../workspace" }
logging_macro = { path = "../logging_macro" }
tokio = { version = "1.18", features = ["fs"]}
trim-margin = "0.1"

View file

@ -0,0 +1,186 @@
use std::collections::HashMap;
use core::cell::OnceCell;
use filesystem::NormalizedPathBuf;
use logging::debug;
use regex::Regex;
use tower_lsp::lsp_types::*;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use url::Url;
use crate::ShaderValidator;
use sourcefile::{SourceMapper, SourceNum};
pub struct DiagnosticsParser<'a, T: ShaderValidator + ?Sized> {
// line_offset: OnceCell<u32>,
line_regex: OnceCell<Regex>,
vendor_querier: &'a T,
}
impl<'a, T: ShaderValidator + ?Sized> DiagnosticsParser<'a, T> {
pub fn new(vendor_querier: &'a T) -> Self {
DiagnosticsParser {
// line_offset: OnceCell::new(),
line_regex: OnceCell::new(),
vendor_querier,
}
}
fn get_line_regex(&self) -> &Regex {
self.line_regex.get_or_init(|| match self.vendor_querier.vendor().as_str() {
"NVIDIA Corporation" => {
Regex::new(r#"^(?P<filepath>\d+)\((?P<linenum>\d+)\) : (?P<severity>error|warning) [A-C]\d+: (?P<output>.+)"#).unwrap()
}
_ => Regex::new(
r#"^(?P<severity>ERROR|WARNING): (?P<filepath>[^?<>*|"\n]+):(?P<linenum>\d+): (?:'.*' :|[a-z]+\(#\d+\)) +(?P<output>.+)$"#,
)
.unwrap(),
})
}
// fn get_line_offset(&self) -> u32 {
// *self.line_offset.get_or_init(|| match self.vendor_querier.vendor().as_str() {
// "ATI Technologies" | "ATI Technologies Inc." | "AMD" => 0,
// _ => 1,
// })
// }
pub fn parse_diagnostics_output(
&self,
output: String,
uri: &NormalizedPathBuf,
source_mapper: &SourceMapper<NormalizedPathBuf>,
// graph: &CachedStableGraph<NormalizedPathBuf, IncludeLine>,
) -> HashMap<Url, Vec<Diagnostic>> {
let output_lines = output.split('\n').collect::<Vec<&str>>();
let mut diagnostics: HashMap<Url, Vec<Diagnostic>> = HashMap::with_capacity(output_lines.len());
debug!("diagnostics regex selected"; "regex" => self.get_line_regex() .as_str());
for line in output_lines {
let diagnostic_capture = match self.get_line_regex().captures(line) {
Some(d) => d,
None => continue,
};
debug!("found match for output line"; "line" => line, "capture" => format!("{:?}", diagnostic_capture));
let msg = diagnostic_capture.name("output").unwrap().as_str();
let line = match diagnostic_capture.name("linenum") {
Some(c) => c.as_str().parse::<u32>().unwrap_or(0),
None => 0,
};
// TODO: line matching maybe
/* let line_text = source_lines[line as usize];
let leading_whitespace = line_text.len() - line_text.trim_start().len(); */
let severity = match diagnostic_capture.name("severity") {
Some(c) => match c.as_str().to_lowercase().as_str() {
"error" => DiagnosticSeverity::ERROR,
"warning" => DiagnosticSeverity::WARNING,
_ => DiagnosticSeverity::INFORMATION,
},
_ => DiagnosticSeverity::INFORMATION,
};
let origin = match diagnostic_capture.name("filepath") {
Some(o) => {
let source_num: SourceNum = o.as_str().parse::<usize>().unwrap().into();
source_mapper.get_node(source_num)
}
None => uri,
};
let diagnostic = Diagnostic {
range: Range::new(
/* Position::new(line, leading_whitespace as u64),
Position::new(line, line_text.len() as u64) */
Position::new(line-1, 0),
Position::new(line-1, 1000),
),
severity: Some(severity),
source: Some("mcglsl".to_string()),
message: msg.trim().into(),
..Default::default()
};
let origin_url = Url::from_file_path(origin).unwrap();
match diagnostics.get_mut(&origin_url) {
Some(d) => d.push(diagnostic),
None => {
diagnostics.insert(origin_url, vec![diagnostic]);
}
};
}
diagnostics
}
}
#[cfg(test)]
mod diagnostics_test {
use filesystem::NormalizedPathBuf;
use sourcefile::SourceMapper;
use trim_margin::MarginTrimmable;
use url::Url;
use crate::{diagnostics_parser::DiagnosticsParser, MockShaderValidator};
#[test]
#[logging_macro::scope]
fn test_nvidia_diagnostics() {
logging::scope(&logging::logger().new(slog_o!("driver" => "nvidia")), || {
let mut mockgl = MockShaderValidator::new();
mockgl.expect_vendor().returning(|| "NVIDIA Corporation".into());
let output = "0(9) : error C0000: syntax error, unexpected '}', expecting ',' or ';' at token \"}\"";
#[cfg(target_family = "unix")]
let path: NormalizedPathBuf = "/home/noah/.minecraft/shaderpacks/test/shaders/final.fsh".into();
#[cfg(target_family = "windows")]
let path: NormalizedPathBuf = "c:\\home\\noah\\.minecraft\\shaderpacks\\test\\shaders\\final.fsh".into();
let mut source_mapper = SourceMapper::new(0);
source_mapper.get_num(&path);
let parser = DiagnosticsParser::new(&mockgl);
let results = parser.parse_diagnostics_output(output.to_string(), &path.parent().unwrap(), &source_mapper);
assert_eq!(results.len(), 1);
let first = results.into_iter().next().unwrap();
assert_eq!(first.0, Url::from_file_path(path).unwrap());
});
}
#[test]
#[logging_macro::scope]
fn test_amd_diagnostics() {
logging::scope(&logging::logger().new(slog_o!("driver" => "amd")), || {
let mut mockgl = MockShaderValidator::new();
mockgl.expect_vendor().returning(|| "ATI Technologies".into());
let output = r#"
|ERROR: 0:1: '' : syntax error: #line
|ERROR: 0:10: '' : syntax error: #line
|ERROR: 0:15: 'varying' : syntax error: syntax error
"#.trim_margin().unwrap();
#[cfg(target_family = "unix")]
let path: NormalizedPathBuf = "/home/noah/.minecraft/shaderpacks/test/shaders/final.fsh".into();
#[cfg(target_family = "windows")]
let path: NormalizedPathBuf = "c:\\home\\noah\\.minecraft\\shaderpacks\\test\\shaders\\final.fsh".into();
let mut source_mapper = SourceMapper::new(0);
source_mapper.get_num(&path);
let parser = DiagnosticsParser::new(&mockgl);
let results = parser.parse_diagnostics_output(output, &path.parent().unwrap(), &source_mapper);
assert_eq!(results.len(), 1);
let first = results.into_iter().next().unwrap();
assert_eq!(first.1.len(), 3);
});
}
}

56
server/opengl/src/lib.rs Normal file
View file

@ -0,0 +1,56 @@
#![feature(once_cell)]
mod opengl_context;
mod opengl_context_facade;
use mockall::automock;
use opengl_context::*;
pub use opengl_context_facade::*;
pub mod diagnostics_parser;
use std::fmt::Debug;
#[automock]
pub trait ShaderValidator {
fn validate(&self, tree_type: TreeType, source: &str) -> Option<String>;
fn vendor(&self) -> String;
}
#[derive(Debug, Clone, Copy)]
pub enum GPUVendor {
NVIDIA, AMD, OTHER // and thats it folks
}
impl From<&str> for GPUVendor {
fn from(s: &str) -> Self {
match s {
"NVIDIA Corporation" => Self::NVIDIA,
"AMD" | "ATI Technologies" | "ATI Technologies Inc." => Self::AMD,
_ => Self::OTHER
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum TreeType {
Fragment,
Vertex,
Geometry,
Compute,
}
impl From<&str> for TreeType {
fn from(ext: &str) -> Self {
if ext == "fsh" {
TreeType::Fragment
} else if ext == "vsh" {
TreeType::Vertex
} else if ext == "gsh" {
TreeType::Geometry
} else if ext == "csh" {
TreeType::Compute
} else {
unreachable!();
}
}
}

View file

@ -0,0 +1,96 @@
use std::ffi::{CStr, CString};
use std::ptr;
use glutin::platform::unix::EventLoopExtUnix;
use logging::info;
use crate::ShaderValidator;
pub(crate) struct Context {
_ctx: glutin::Context<glutin::PossiblyCurrent>,
}
impl Context {
pub fn default() -> Context {
let events_loop = glutin::event_loop::EventLoop::<()>::new_any_thread();
let gl_window = glutin::ContextBuilder::new()
.build_headless(&*events_loop, glutin::dpi::PhysicalSize::new(1, 1))
.unwrap();
let gl_window = unsafe {
let gl_window = gl_window.make_current().unwrap();
gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
gl_window
};
let gl_ctx = Context { _ctx: gl_window };
unsafe {
info!(
"OpenGL device";
"vendor" => gl_ctx.vendor(),
"version" => String::from_utf8(CStr::from_ptr(gl::GetString(gl::VERSION) as *const _).to_bytes().to_vec()).unwrap(),
"renderer" => String::from_utf8(CStr::from_ptr(gl::GetString(gl::RENDERER) as *const _).to_bytes().to_vec()).unwrap()
);
}
gl_ctx
}
unsafe fn compile_and_get_shader_log(&self, shader: gl::types::GLuint, source: &str) -> Option<String> {
let mut success = i32::from(gl::FALSE);
let c_str_frag = CString::new(source).unwrap();
gl::ShaderSource(shader, 1, &c_str_frag.as_ptr(), ptr::null());
gl::CompileShader(shader);
// Check for shader compilation errors
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success);
let result = if success != i32::from(gl::TRUE) {
let mut info_len: gl::types::GLint = 0;
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut info_len);
let mut info = vec![0u8; info_len as usize];
gl::GetShaderInfoLog(
shader,
info_len as gl::types::GLsizei,
ptr::null_mut(),
info.as_mut_ptr() as *mut gl::types::GLchar,
);
info.set_len((info_len - 1) as usize); // ignore null for str::from_utf8
Some(String::from_utf8(info).unwrap())
} else { None };
gl::DeleteShader(shader);
result
}
}
impl ShaderValidator for Context {
fn validate(&self, tree_type: super::TreeType, source: &str) -> Option<String> {
unsafe {
match tree_type {
crate::TreeType::Fragment => {
// Fragment shader
let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
self.compile_and_get_shader_log(fragment_shader, source)
}
crate::TreeType::Vertex => {
// Vertex shader
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
self.compile_and_get_shader_log(vertex_shader, source)
}
crate::TreeType::Geometry => {
// Geometry shader
let geometry_shader = gl::CreateShader(gl::GEOMETRY_SHADER);
self.compile_and_get_shader_log(geometry_shader, source)
}
crate::TreeType::Compute => {
// Compute shader
let compute_shader = gl::CreateShader(gl::COMPUTE_SHADER);
self.compile_and_get_shader_log(compute_shader, source)
}
}
}
}
fn vendor(&self) -> String {
unsafe { String::from_utf8(CStr::from_ptr(gl::GetString(gl::VENDOR) as *const _).to_bytes().to_vec()).unwrap() }
}
}

View file

@ -0,0 +1,87 @@
use std::{
sync::{
mpsc::{self, Receiver, SyncSender},
Arc,
},
thread,
};
use crate::{Context, ShaderValidator};
enum ClientMessage {
Validate { tree_type: crate::TreeType, source: Arc<str> },
Vendor,
}
enum ServerMessage {
Validate(Option<String>),
Vendor(String),
}
///
pub struct ContextFacade {
start_chan: SyncSender<()>,
client_tx: SyncSender<ClientMessage>,
server_rx: Receiver<ServerMessage>,
}
impl ContextFacade {
pub fn default() -> Self {
let (client_tx, client_rx) = mpsc::sync_channel::<ClientMessage>(1);
let (server_tx, server_rx) = mpsc::sync_channel::<ServerMessage>(1);
let (start_chan, start_chan_recv) = mpsc::sync_channel::<()>(1);
thread::spawn(move || {
start_chan_recv.recv().unwrap();
let opengl_ctx = Context::default();
loop {
match client_rx.recv() {
Ok(msg) => {
if let ClientMessage::Validate { tree_type, source } = msg {
server_tx
.send(ServerMessage::Validate(opengl_ctx.validate(tree_type, &source)))
.unwrap();
} else {
server_tx.send(ServerMessage::Vendor(opengl_ctx.vendor())).unwrap();
}
}
Err(_) => return,
}
start_chan_recv.recv().unwrap();
}
});
ContextFacade {
start_chan,
client_tx,
server_rx,
}
}
}
impl ShaderValidator for ContextFacade {
fn validate(&self, tree_type: crate::TreeType, source: &str) -> Option<String> {
self.start_chan.send(()).unwrap();
match self.client_tx.send(ClientMessage::Validate {
tree_type,
source: Arc::from(source),
}) {
Ok(_) => match self.server_rx.recv().unwrap() {
ServerMessage::Validate(output) => output,
ServerMessage::Vendor(_) => panic!("expected validate reply, got vendor"),
},
Err(e) => panic!("error sending vendor message: {:?}", e),
}
}
fn vendor(&self) -> String {
self.start_chan.send(()).unwrap();
match self.client_tx.send(ClientMessage::Vendor) {
Ok(_) => match self.server_rx.recv().unwrap() {
ServerMessage::Validate(_) => panic!("expected vendor reply, got validate"),
ServerMessage::Vendor(resp) => resp,
},
Err(e) => panic!("error sending vendor message: {:?}", e),
}
}
}