From d925dc95c6a166ca5931514a5d788065623a912e Mon Sep 17 00:00:00 2001
From: chyyran <ronny@ronnychan.ca>
Date: Sat, 11 Feb 2023 15:37:21 -0500
Subject: [PATCH] capi: expose helpers to ensure ABI version compatibility

---
 include/README.md                         | 11 ++++--
 include/librashader.h                     | 36 +++++++++++++++++-
 include/librashader_ld.h                  | 26 +++++++++++++
 librashader-capi/cbindgen.toml            |  4 ++
 librashader-capi/src/error.rs             |  2 +-
 librashader-capi/src/lib.rs               |  8 ++--
 librashader-capi/src/presets.rs           |  2 +-
 librashader-capi/src/runtime/d3d11/mod.rs |  2 +-
 librashader-capi/src/runtime/d3d12/mod.rs |  2 +-
 librashader-capi/src/runtime/gl/mod.rs    |  2 +-
 librashader-capi/src/runtime/mod.rs       |  2 +-
 librashader-capi/src/runtime/vk/mod.rs    |  2 +-
 librashader-capi/src/version.rs           | 45 +++++++++++++++++++++++
 13 files changed, 127 insertions(+), 17 deletions(-)
 create mode 100644 librashader-capi/src/version.rs

diff --git a/include/README.md b/include/README.md
index b5540e6..b1c5aaf 100644
--- a/include/README.md
+++ b/include/README.md
@@ -20,12 +20,15 @@ A basic example of using `librashader_ld.h` to load a shader preset.
 #include "librashader_ld.h"
 
 libra_gl_filter_chain_t load_gl_filter_chain(libra_gl_loader_t opengl, const char *preset_path) {
-  // Ideally you should not need to check for success. If loading fails, then everything should just no-op.
-
-  // If you want to check for failure, all function pointers are intialized by default to their corresponding
-  // __librashader__noop_* functions in librashader.h. 
   libra_instance_t librashader = librashader_load_instance();
 
+  // If the instance ABI version is 0, then either librashader is not found, or 
+  // the ABI is incompatible.
+  if (librashader.instance_abi_version() == 0) {
+    std::cout << "Could not load librashader\n";
+    return NULL;
+  }
+  
   libra_shader_preset_t preset;
   libra_error_t error = librashader.preset_create(preset_path, &preset);
   if (error != NULL) {
diff --git a/include/librashader.h b/include/librashader.h
index c904f51..91897a7 100644
--- a/include/librashader.h
+++ b/include/librashader.h
@@ -139,6 +139,7 @@ typedef struct libra_preset_param_list_t {
 typedef const void *(*libra_gl_loader_t)(const char*);
 #endif
 
+/// API version type alias.
 typedef size_t LIBRASHADER_API_VERSION;
 
 #if defined(LIBRA_RUNTIME_OPENGL)
@@ -384,6 +385,14 @@ typedef struct frame_d3d12_opt_t {
 } frame_d3d12_opt_t;
 #endif
 
+typedef size_t LIBRASHADER_ABI_VERSION;
+
+/// Function pointer definition for libra_abi_version
+typedef LIBRASHADER_ABI_VERSION (*PFN_libra_instance_abi_version)(void);
+
+/// Function pointer definition for libra_abi_version
+typedef LIBRASHADER_API_VERSION (*PFN_libra_instance_api_version)(void);
+
 /// Function pointer definition for
 ///libra_preset_create
 typedef libra_error_t (*PFN_libra_preset_create)(const char *filename, libra_shader_preset_t *out);
@@ -698,10 +707,29 @@ typedef libra_error_t (*PFN_libra_d3d12_filter_chain_get_active_pass_count)(libr
 typedef libra_error_t (*PFN_libra_d3d12_filter_chain_free)(libra_d3d12_filter_chain_t *chain);
 #endif
 
-/// The current version of the librashader API/ABI.
+/// The current version of the librashader API.
 /// Pass this into `version` for config structs.
+///
+/// API versions are backwards compatible. It is valid to load
+/// a librashader C API instance for all API versions less than
+/// or equal to LIBRASHADER_CURRENT_VERSION, and subsequent API
+/// versions must remain backwards compatible.
+/// ## API Versions
+/// - API version 0: 0.1.0
 #define LIBRASHADER_CURRENT_VERSION 0
 
+/// The current version of the librashader ABI.
+/// Used by the loader to check ABI compatibility.
+///
+/// ABI version 0 is reserved as a sentinel value.
+///
+/// ABI versions are not backwards compatible. It is not
+/// valid to load a librashader C API instance for any ABI
+/// version not equal to LIBRASHADER_CURRENT_ABI.
+/// ## ABI Versions
+/// - ABI version 1: 0.1.0
+#define LIBRASHADER_CURRENT_ABI 1
+
 #ifdef __cplusplus
 extern "C" {
 #endif // __cplusplus
@@ -1306,6 +1334,12 @@ libra_error_t libra_d3d12_filter_chain_get_active_pass_count(libra_d3d12_filter_
 libra_error_t libra_d3d12_filter_chain_free(libra_d3d12_filter_chain_t *chain);
 #endif
 
+/// Get the ABI version of the loaded instance.
+LIBRASHADER_ABI_VERSION libra_instance_abi_version(void);
+
+/// Get the API version of the loaded instance.
+LIBRASHADER_API_VERSION libra_instance_api_version(void);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif // __cplusplus
diff --git a/include/librashader_ld.h b/include/librashader_ld.h
index c784a2e..3f71014 100644
--- a/include/librashader_ld.h
+++ b/include/librashader_ld.h
@@ -64,6 +64,10 @@ typedef void* _LIBRASHADER_IMPL_HANDLE;
 
 #include "librashader.h"
 
+size_t __librashader__noop_instance_abi_version() { return 0; }
+
+size_t __librashader__noop_instance_api_version() { return 0; }
+
 LIBRA_ERRNO __librashader__noop_error_errno(libra_error_t error) {
     return LIBRA_ERRNO_UNKNOWN_ERROR;
 }
@@ -302,6 +306,17 @@ libra_error_t __librashader__noop_d3d12_filter_chain_get_active_pass_count(
 }
 #endif
 typedef struct libra_instance_t {
+    /// Get the supported ABI version of the loaded instance.
+    ///
+    /// The null instance has ABI version 0. Any valid loaded
+    /// instance must have ABI version greater than or equal to 1.
+    PFN_libra_instance_abi_version instance_abi_version;
+   
+    /// Get the supported API version of the loaded instance.
+    ///
+    /// The null instance has API version 0.
+    PFN_libra_instance_api_version instance_api_version;
+
     /// Load a preset.
     ///
     /// ## Safety
@@ -815,6 +830,9 @@ typedef struct libra_instance_t {
 
 libra_instance_t __librashader_make_null_instance() {
     return libra_instance_t {
+        .instance_abi_version = __librashader__noop_instance_abi_version,
+        .instance_api_version = __librashader__noop_instance_api_version,
+
         .preset_create = __librashader__noop_preset_create,
         .preset_free = __librashader__noop_preset_free,
         .preset_set_param = __librashader__noop_preset_set_param,
@@ -921,6 +939,14 @@ libra_instance_t librashader_load_instance() {
         return instance;
     }
 
+    _LIBRASHADER_ASSIGN(librashader, instance, instance_abi_version);
+    _LIBRASHADER_ASSIGN(librashader, instance, instance_api_version);
+
+    // Ensure ABI matches.
+    if (instance.instance_abi_version() != LIBRASHADER_CURRENT_ABI) {
+        return instance;
+    }
+
     _LIBRASHADER_ASSIGN(librashader, instance, preset_create);
     _LIBRASHADER_ASSIGN(librashader, instance, preset_free);
     _LIBRASHADER_ASSIGN(librashader, instance, preset_set_param);
diff --git a/librashader-capi/cbindgen.toml b/librashader-capi/cbindgen.toml
index 7f35f03..ef39ebd 100644
--- a/librashader-capi/cbindgen.toml
+++ b/librashader-capi/cbindgen.toml
@@ -87,6 +87,10 @@ prefix_with_name = true
 
 [export]
 include = [
+    # instance
+    "PFN_libra_instance_abi_version",
+    "PFN_libra_instance_api_version",
+
     # preset
     "PFN_libra_preset_create",
     "PFN_libra_preset_free",
diff --git a/librashader-capi/src/error.rs b/librashader-capi/src/error.rs
index 9f04b52..c56dfa0 100644
--- a/librashader-capi/src/error.rs
+++ b/librashader-capi/src/error.rs
@@ -1,4 +1,4 @@
-//! The librashader error C API. (`libra_error_*`).
+//! librashader error C API. (`libra_error_*`).
 use std::any::Any;
 use std::ffi::{c_char, CString};
 use std::mem::MaybeUninit;
diff --git a/librashader-capi/src/lib.rs b/librashader-capi/src/lib.rs
index 2eed5e0..fa6fc89 100644
--- a/librashader-capi/src/lib.rs
+++ b/librashader-capi/src/lib.rs
@@ -79,12 +79,10 @@ pub mod presets;
 pub mod reflect;
 
 pub mod runtime;
+pub mod version;
 
-pub type LIBRASHADER_API_VERSION = usize;
-
-/// The current version of the librashader API/ABI.
-/// Pass this into `version` for config structs.
-pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
+pub use version::LIBRASHADER_ABI_VERSION;
+pub use version::LIBRASHADER_API_VERSION;
 
 #[allow(dead_code)]
 const fn assert_thread_safe<T: Send + Sync>() {}
diff --git a/librashader-capi/src/presets.rs b/librashader-capi/src/presets.rs
index 4179741..591c4f7 100644
--- a/librashader-capi/src/presets.rs
+++ b/librashader-capi/src/presets.rs
@@ -1,4 +1,4 @@
-//! The librashader preset C API (`libra_preset_*`).
+//! librashader preset C API (`libra_preset_*`).
 use crate::ctypes::libra_shader_preset_t;
 use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
 use crate::ffi::extern_fn;
diff --git a/librashader-capi/src/runtime/d3d11/mod.rs b/librashader-capi/src/runtime/d3d11/mod.rs
index 5557f2b..1a42c7a 100644
--- a/librashader-capi/src/runtime/d3d11/mod.rs
+++ b/librashader-capi/src/runtime/d3d11/mod.rs
@@ -1,4 +1,4 @@
-//! C API for the librashader D3D12 Runtime (`libra_d3d11_*`)
+//! C API for the librashader D3D12 Runtime (`libra_d3d11_*`).
 
 mod filter_chain;
 pub use filter_chain::*;
diff --git a/librashader-capi/src/runtime/d3d12/mod.rs b/librashader-capi/src/runtime/d3d12/mod.rs
index 3c6ecda..e45700f 100644
--- a/librashader-capi/src/runtime/d3d12/mod.rs
+++ b/librashader-capi/src/runtime/d3d12/mod.rs
@@ -1,4 +1,4 @@
-//! C API for the librashader D3D12 Runtime (`libra_d3d12_*`)
+//! C API for the librashader D3D12 Runtime (`libra_d3d12_*`).
 
 mod filter_chain;
 pub use filter_chain::*;
diff --git a/librashader-capi/src/runtime/gl/mod.rs b/librashader-capi/src/runtime/gl/mod.rs
index f33b437..1d4a8f1 100644
--- a/librashader-capi/src/runtime/gl/mod.rs
+++ b/librashader-capi/src/runtime/gl/mod.rs
@@ -1,4 +1,4 @@
-//! C API for the librashader OpenGL Runtime (`libra_gl_*`)
+//! C API for the librashader OpenGL Runtime (`libra_gl_*`).
 
 mod filter_chain;
 pub use filter_chain::*;
diff --git a/librashader-capi/src/runtime/mod.rs b/librashader-capi/src/runtime/mod.rs
index 5424b22..62e8efc 100644
--- a/librashader-capi/src/runtime/mod.rs
+++ b/librashader-capi/src/runtime/mod.rs
@@ -1,4 +1,4 @@
-//! librashader runtime C APIs
+//! librashader runtime C APIs.
 #[doc(cfg(feature = "runtime-opengl"))]
 #[cfg(feature = "runtime-opengl")]
 pub mod gl;
diff --git a/librashader-capi/src/runtime/vk/mod.rs b/librashader-capi/src/runtime/vk/mod.rs
index 167e387..0b250cf 100644
--- a/librashader-capi/src/runtime/vk/mod.rs
+++ b/librashader-capi/src/runtime/vk/mod.rs
@@ -1,4 +1,4 @@
-//! C API for the librashader OpenGL Runtime (`libra_gl_*`)
+//! C API for the librashader OpenGL Runtime (`libra_vk_*`).
 
 mod filter_chain;
 pub use filter_chain::*;
diff --git a/librashader-capi/src/version.rs b/librashader-capi/src/version.rs
new file mode 100644
index 0000000..86cf558
--- /dev/null
+++ b/librashader-capi/src/version.rs
@@ -0,0 +1,45 @@
+//! librashader instance version helpers.
+
+/// API version type alias.
+pub type LIBRASHADER_API_VERSION = usize;
+pub type LIBRASHADER_ABI_VERSION = usize;
+
+/// The current version of the librashader API.
+/// Pass this into `version` for config structs.
+///
+/// API versions are backwards compatible. It is valid to load
+/// a librashader C API instance for all API versions less than
+/// or equal to LIBRASHADER_CURRENT_VERSION, and subsequent API
+/// versions must remain backwards compatible.
+/// ## API Versions
+/// - API version 0: 0.1.0
+pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
+
+/// The current version of the librashader ABI.
+/// Used by the loader to check ABI compatibility.
+///
+/// ABI version 0 is reserved as a sentinel value.
+///
+/// ABI versions are not backwards compatible. It is not
+/// valid to load a librashader C API instance for any ABI
+/// version not equal to LIBRASHADER_CURRENT_ABI.
+/// ## ABI Versions
+/// - ABI version 0: null instance (unloaded)
+/// - ABI version 1: 0.1.0
+pub const LIBRASHADER_CURRENT_ABI: LIBRASHADER_ABI_VERSION = 1;
+
+/// Function pointer definition for libra_abi_version
+pub type PFN_libra_instance_abi_version = extern "C" fn() -> LIBRASHADER_ABI_VERSION;
+/// Get the ABI version of the loaded instance.
+#[no_mangle]
+pub extern "C" fn libra_instance_abi_version() -> LIBRASHADER_ABI_VERSION {
+    LIBRASHADER_CURRENT_ABI
+}
+
+/// Function pointer definition for libra_abi_version
+pub type PFN_libra_instance_api_version = extern "C" fn() -> LIBRASHADER_API_VERSION;
+/// Get the API version of the loaded instance.
+#[no_mangle]
+pub extern "C" fn libra_instance_api_version() -> LIBRASHADER_API_VERSION {
+    LIBRASHADER_CURRENT_VERSION
+}