diff --git a/include/resource/texture_array.h b/include/resource/texture_array.h deleted file mode 100644 index 71c6d78..0000000 --- a/include/resource/texture_array.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef ARE_INCLUDE_RESOURCE_TEXTURE_ARRAY_H -#define ARE_INCLUDE_RESOURCE_TEXTURE_ARRAY_H - -#include "basic/types.h" -#include - -namespace are { - -/** - * @brief 2D texture array wrapper for PBR textures - */ -class TextureArray { -public: - /** - * @brief Construct texture array - */ - TextureArray(); - - /** - * @brief Destroy texture array - */ - ~TextureArray(); - - TextureArray(const TextureArray&) = delete; - TextureArray& operator=(const TextureArray&) = delete; - - TextureArray(TextureArray&& other) noexcept; - TextureArray& operator=(TextureArray&& other) noexcept; - - /** - * @brief Create empty texture array storage - * @param width Layer width - * @param height Layer height - * @param layers Layer count - * @param internal_format OpenGL internal format (e.g. GL_RGBA8, GL_RGBA16F) - * @param srgb True if texture should be treated as sRGB (use GL_SRGB8_ALPHA8) - * @return True on success - */ - bool create(uint width, uint height, uint layers, uint internal_format); - - /** - * @brief Upload one layer (expects RGBA8 data) - * @param layer Layer index - * @param data Pixel data pointer - * @param width Data width - * @param height Data height - */ - bool upload_rgba8(uint layer, const void* data, uint width, uint height); - - /** - * @brief Bind to texture unit - */ - void bind(uint unit) const; - - /** - * @brief Release OpenGL resources - */ - void release(); - - /** - * @brief Get OpenGL handle - */ - TextureHandle get_handle() const { return handle_; } - - uint get_width() const { return width_; } - uint get_height() const { return height_; } - uint get_layers() const { return layers_; } - - bool is_valid() const { return handle_ != INVALID_HANDLE; } - -private: - TextureHandle handle_; - uint width_; - uint height_; - uint layers_; - uint internal_format_; -}; - -} // namespace are - -#endif // ARE_INCLUDE_RESOURCE_TEXTURE_ARRAY_H diff --git a/include/scene/pbr_material_gpu.h b/include/scene/pbr_material_gpu.h deleted file mode 100644 index 68d38d8..0000000 --- a/include/scene/pbr_material_gpu.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ARE_INCLUDE_SCENE_PBR_MATERIAL_GPU_H -#define ARE_INCLUDE_SCENE_PBR_MATERIAL_GPU_H - -#include "basic/types.h" - -namespace are { - -/** - * @brief PBR material feature flags - */ -enum class PbrMaterialFlags : uint { - NONE = 0u, - HAS_BASE_COLOR_TEX = 1u << 0, - HAS_NORMAL_TEX = 1u << 1, - HAS_METAL_ROUGH_TEX = 1u << 2, - HAS_EMISSIVE_TEX = 1u << 3, - DOUBLE_SIDED = 1u << 4, - ALPHA_MASK = 1u << 5, - ALPHA_BLEND = 1u << 6 -}; - -inline PbrMaterialFlags operator|(PbrMaterialFlags a, PbrMaterialFlags b) { - return static_cast(static_cast(a) | static_cast(b)); -} - -inline uint to_uint(PbrMaterialFlags f) { - return static_cast(f); -} - -/** - * @brief GPU-friendly PBR material (std430 aligned by vec4/uvec4) - * @note All fields are designed to be consumed by GLSL std430 without padding issues. - */ -struct PbrMaterialGpu { - Vec4 base_color_factor_; ///< rgb = baseColor, a = alpha - Vec4 emissive_factor_; ///< rgb = emissive, a = unused - Vec4 mr_normal_flags_; ///< x=metallic, y=roughness, z=normal_scale, w=flags (uint bits in float) - - // texture layer indices (per texture array). TEX_INVALID if none. - glm::uvec4 tex0_; ///< x=baseColor, y=normal, z=metalRough, w=emissive - glm::uvec4 tex1_; ///< reserved -}; - -constexpr uint TEX_INVALID = 0xFFFFFFFFu; - -} // namespace are - -#endif // ARE_INCLUDE_SCENE_PBR_MATERIAL_GPU_H diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp index 0679286..a34c5eb 100644 --- a/shaders/raytracing.comp +++ b/shaders/raytracing.comp @@ -6,6 +6,11 @@ #define MAX_FLOAT 3.402823466e38 #define RR_THRESHOLD 0.1 +#define MATERIAL_DIFFUSE 0 +#define MATERIAL_METAL 1 +#define MATERIAL_DIELECTRIC 2 +#define MATERIAL_EMISSIVE 3 + #define LIGHT_DIRECTIONAL 0 #define LIGHT_POINT 1 #define LIGHT_SPOT 2 @@ -25,14 +30,14 @@ layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id; layout(binding = 3, rgba32f) uniform image2D output_image; layout(binding = 4, rgba32f) uniform image2D accumulation_image; -const uint TEX_INVALID = 4294967295u; - -struct PbrMaterialGpu { - vec4 base_color_factor; // rgb + alpha - vec4 emissive_factor; // rgb - vec4 mr_normal_flags; // x=metallic, y=roughness, z=normal_scale, w=flags (uint bits in float) - uvec4 tex0; // baseColor, normal, metalRough, emissive (layer indices) - uvec4 tex1; +struct Material { + vec3 albedo; + float metallic; + vec3 emission; + float roughness; + int type; + float ior; + vec2 padding; }; struct Light { @@ -82,7 +87,7 @@ struct TriangleGpu { vec4 uv2; // xy uv2 }; -layout(std430, binding = 0) readonly buffer MaterialBuffer { PbrMaterialGpu materials[]; }; +layout(std430, binding = 0) readonly buffer MaterialBuffer { Material materials[]; }; layout(std430, binding = 1) readonly buffer LightBuffer { Light lights[]; }; layout(std430, binding = 2) readonly buffer BVHNodeBuffer { BVHNodeGpu bvh_nodes[]; }; layout(std430, binding = 3) readonly buffer TriangleBuffer { TriangleGpu bvh_tris[]; }; @@ -100,15 +105,32 @@ uniform uint u_bvh_node_count; // Utility // ============================================================================ +/** + * @brief Check if vector is near zero + */ bool near_zero(vec3 v) { return (abs(v.x) < EPSILON) && (abs(v.y) < EPSILON) && (abs(v.z) < EPSILON); } +/** + * @brief Reflect vector around normal + */ vec3 reflect_vector(vec3 v, vec3 n) { return v - 2.0 * dot(v, n) * n; } +/** + * @brief Refract vector through surface + */ +vec3 refract_vector(vec3 uv, vec3 n, float etai_over_etat) { + float cos_theta = min(dot(-uv, n), 1.0); + vec3 r_out_perp = etai_over_etat * (uv + cos_theta * n); + vec3 r_out_parallel = -sqrt(abs(1.0 - dot(r_out_perp, r_out_perp))) * n; + return r_out_perp + r_out_parallel; +} + uint as_uint(float f) { return floatBitsToUint(f); } +float as_float(uint u) { return uintBitsToFloat(u); } // ============================================================================ // RNG (PCG) @@ -144,8 +166,12 @@ vec3 random_unit_vector(inout uint seed) { // Camera ray // ============================================================================ -Ray generate_camera_ray_center(ivec2 pixel_coords, ivec2 image_size) { - vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size); +/** + * @brief Generate primary ray in world space + */ +Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size, inout uint seed) { + vec2 jitter = vec2(random_float(seed), random_float(seed)); + vec2 uv = (vec2(pixel_coords) + jitter) / vec2(image_size); vec2 ndc = uv * 2.0 - 1.0; vec4 p_near = u_inv_view_projection * vec4(ndc, 0.0, 1.0); @@ -163,6 +189,9 @@ Ray generate_camera_ray_center(ivec2 pixel_coords, ivec2 image_size) { // Intersection // ============================================================================ +/** + * @brief Ray-AABB intersection + */ bool intersect_aabb(Ray ray, vec3 aabb_min, vec3 aabb_max, float t_max) { vec3 inv_d = 1.0 / ray.direction; vec3 t0 = (aabb_min - ray.origin) * inv_d; @@ -177,6 +206,9 @@ bool intersect_aabb(Ray ray, vec3 aabb_min, vec3 aabb_max, float t_max) { return (tmax2 >= max(tmin, 0.0)) && (tmin <= t_max); } +/** + * @brief Moller-Trumbore triangle intersection + */ bool intersect_triangle(Ray ray, TriangleGpu tri, inout HitInfo hit) { vec3 v0 = tri.v0_material.xyz; vec3 v1 = tri.v1.xyz; @@ -219,12 +251,17 @@ bool intersect_triangle(Ray ray, TriangleGpu tri, inout HitInfo hit) { return true; } +/** + * @brief BVH traversal (closest hit) + */ HitInfo trace_ray_bvh(Ray ray) { HitInfo hit; hit.hit = false; hit.t = MAX_FLOAT; - if (!u_use_bvh || u_bvh_node_count == 0u) return hit; + if (!u_use_bvh || u_bvh_node_count == 0u) { + return hit; + } uint stack[64]; int sp = 0; @@ -258,6 +295,9 @@ HitInfo trace_ray_bvh(Ray ray) { return hit; } +/** + * @brief Any-hit BVH for shadow ray + */ bool trace_any_bvh(Ray ray, float t_max) { if (!u_use_bvh || u_bvh_node_count == 0u) return false; @@ -301,6 +341,10 @@ bool trace_any_bvh(Ray ray, float t_max) { // Primary-ray fast path via G-Buffer // ============================================================================ +/** + * @brief Read primary hit from G-Buffer if current pixel has geometry + * @note Uses g_position.w as "valid" marker (your gbuffer writes 1.0 on hits, clear is 0). + */ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { HitInfo hit; hit.hit = false; @@ -311,47 +355,45 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { hit.material_id = 0u; vec4 pos = imageLoad(g_position, pixel_coords); - if (pos.w <= 0.5) return hit; + if (pos.w <= 0.5) { + return hit; + } + + vec3 p = pos.xyz; + vec3 n = normalize(imageLoad(g_normal, pixel_coords).xyz); + + // integer material id + uint mid = imageLoad(g_material_id, pixel_coords).r; hit.hit = true; - hit.position = pos.xyz; - hit.normal = normalize(imageLoad(g_normal, pixel_coords).xyz); - hit.material_id = imageLoad(g_material_id, pixel_coords).r; + hit.position = p; + hit.normal = n; + hit.material_id = mid; + + // For RR/any debug usage; path tracing uses this as starting point only. + hit.t = length(p - ray.origin); - // Only for traversal cutoff, keep consistent - hit.t = length(hit.position - ray.origin); return hit; } // ============================================================================ -// Material fetch +// Material + scattering // ============================================================================ -PbrMaterialGpu fetch_material(uint material_id) { - uint cnt = uint(materials.length()); - if (material_id < cnt) return materials[material_id]; - - PbrMaterialGpu m; - m.base_color_factor = vec4(0.5, 0.5, 0.5, 1.0); - m.emissive_factor = vec4(0.0); - m.mr_normal_flags = vec4(0.0, 0.5, 1.0, uintBitsToFloat(0u)); - m.tex0 = uvec4(TEX_INVALID); - m.tex1 = uvec4(TEX_INVALID); - return m; +vec3 fresnel_schlick(float cos_theta, vec3 f0) { + return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); } -vec3 environment_color(vec3 dir) { - return vec3(0.1, 0.1, 0.15); +float fresnel_dielectric(float cos_theta, float ior) { + float r0 = (1.0 - ior) / (1.0 + ior); + r0 = r0 * r0; + return r0 + (1.0 - r0) * pow(1.0 - cos_theta, 5.0); } -// ============================================================================ -// Scattering (temporary, PBR v1 will be GGX sampling later) -// ============================================================================ - -ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, PbrMaterialGpu mat, inout uint seed) { +ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { ScatterResult r; r.scattered = true; - r.attenuation = mat.base_color_factor.rgb; + r.attenuation = mat.albedo; vec3 dir = hit.normal + random_unit_vector(seed); if (near_zero(dir)) dir = hit.normal; @@ -361,35 +403,61 @@ ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, PbrMaterialGpu mat, inout return r; } -ScatterResult scatter_metal_like(Ray ray_in, HitInfo hit, PbrMaterialGpu mat, inout uint seed) { +ScatterResult scatter_metal(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { ScatterResult r; - float roughness = clamp(mat.mr_normal_flags.y, 0.0, 1.0); vec3 reflected = reflect_vector(normalize(ray_in.direction), hit.normal); - vec3 fuzz = roughness * random_in_unit_sphere(seed); + vec3 fuzz = mat.roughness * random_in_unit_sphere(seed); vec3 dir = reflected + fuzz; r.scattered = dot(dir, hit.normal) > 0.0; - r.attenuation = mat.base_color_factor.rgb; + r.attenuation = mat.albedo; r.scattered_ray.origin = hit.position + hit.normal * EPSILON; r.scattered_ray.direction = normalize(dir); return r; } -ScatterResult scatter_pbr(Ray ray_in, HitInfo hit, PbrMaterialGpu mat, inout uint seed) { - // Simple blend by metallic (not physically accurate, but compiles & runs) - float metallic = clamp(mat.mr_normal_flags.x, 0.0, 1.0); - if (random_float(seed) < metallic) { - return scatter_metal_like(ray_in, hit, mat, seed); +ScatterResult scatter_dielectric(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { + ScatterResult r; + r.scattered = true; + r.attenuation = vec3(1.0); + + vec3 unit_dir = normalize(ray_in.direction); + float cos_theta = min(dot(-unit_dir, hit.normal), 1.0); + float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); + + float refraction_ratio = dot(unit_dir, hit.normal) < 0.0 ? (1.0 / mat.ior) : mat.ior; + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + float reflect_prob = fresnel_dielectric(cos_theta, refraction_ratio); + + vec3 dir; + if (cannot_refract || random_float(seed) < reflect_prob) { + dir = reflect_vector(unit_dir, hit.normal); + } else { + dir = refract_vector(unit_dir, hit.normal, refraction_ratio); } - return scatter_diffuse(ray_in, hit, mat, seed); + + r.scattered_ray.origin = hit.position + dir * EPSILON; + r.scattered_ray.direction = normalize(dir); + return r; +} + +ScatterResult scatter_ray(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { + if (mat.type == MATERIAL_DIFFUSE) return scatter_diffuse(ray_in, hit, mat, seed); + if (mat.type == MATERIAL_METAL) return scatter_metal(ray_in, hit, mat, seed); + if (mat.type == MATERIAL_DIELECTRIC) return scatter_dielectric(ray_in, hit, mat, seed); + + ScatterResult r; + r.scattered = false; + r.attenuation = vec3(0.0); + return r; } // ============================================================================ -// Direct lighting (simple diffuse only, temporary) +// Direct lighting (with shadow ray) // ============================================================================ -vec3 eval_direct_lighting(HitInfo hit, PbrMaterialGpu mat, inout uint seed) { +vec3 eval_direct_lighting(HitInfo hit, Material mat, inout uint seed) { if (u_light_count == 0u) return vec3(0.0); uint light_idx = uint(random_float(seed) * float(u_light_count)) % u_light_count; @@ -425,7 +493,7 @@ vec3 eval_direct_lighting(HitInfo hit, PbrMaterialGpu mat, inout uint seed) { if (trace_any_bvh(shadow_ray, t_max)) return vec3(0.0); float pdf_light = 1.0 / float(u_light_count); - vec3 brdf = mat.base_color_factor.rgb * INV_PI; + vec3 brdf = mat.albedo * INV_PI; return brdf * radiance * n_dot_l / max(pdf_light, EPSILON); } @@ -433,42 +501,81 @@ vec3 eval_direct_lighting(HitInfo hit, PbrMaterialGpu mat, inout uint seed) { // Path tracing // ============================================================================ +Material fetch_material(uint material_id) { + uint cnt = uint(materials.length()); + if (material_id < cnt) return materials[material_id]; + + Material m; + m.albedo = vec3(0.5); + m.metallic = 0.0; + m.emission = vec3(0.0); + m.roughness = 0.5; + m.type = MATERIAL_DIFFUSE; + m.ior = 1.5; + return m; +} + +vec3 environment_color(vec3 dir) { + return vec3(0.1, 0.1, 0.15); +} + +Ray generate_camera_ray_center(ivec2 pixel_coords, ivec2 image_size) { + vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size); + vec2 ndc = uv * 2.0 - 1.0; + + vec4 p_near = u_inv_view_projection * vec4(ndc, 0.0, 1.0); + vec4 p_far = u_inv_view_projection * vec4(ndc, 1.0, 1.0); + vec3 near_ws = p_near.xyz / p_near.w; + vec3 far_ws = p_far.xyz / p_far.w; + + Ray r; + r.origin = near_ws; + r.direction = normalize(far_ws - near_ws); + return r; +} + +/** + * @brief Trace path with primary-ray G-Buffer acceleration + */ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint seed) { Ray ray = generate_camera_ray_center(pixel_coords, image_size); vec3 radiance = vec3(0.0); vec3 throughput = vec3(1.0); + // Depth 0: try G-Buffer hit first HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords); - uint depth_start = 0u; - if (hit0.hit) { - PbrMaterialGpu mat0 = fetch_material(hit0.material_id); + Material mat0 = fetch_material(hit0.material_id); - radiance += throughput * mat0.emissive_factor.rgb; - radiance += throughput * eval_direct_lighting(hit0, mat0, seed); + radiance += throughput * mat0.emission; + if (mat0.type == MATERIAL_DIFFUSE) { + radiance += throughput * eval_direct_lighting(hit0, mat0, seed); + } - ScatterResult sc0 = scatter_pbr(ray, hit0, mat0, seed); + ScatterResult sc0 = scatter_ray(ray, hit0, mat0, seed); if (!sc0.scattered) return radiance; throughput *= sc0.attenuation; ray = sc0.scattered_ray; - depth_start = 1u; } - for (uint depth = depth_start; depth < u_max_depth; ++depth) { + // Subsequent bounces: BVH + for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) { HitInfo hit = trace_ray_bvh(ray); if (!hit.hit) { radiance += throughput * environment_color(ray.direction); break; } - PbrMaterialGpu mat = fetch_material(hit.material_id); + Material mat = fetch_material(hit.material_id); - radiance += throughput * mat.emissive_factor.rgb; - radiance += throughput * eval_direct_lighting(hit, mat, seed); + radiance += throughput * mat.emission; + if (mat.type == MATERIAL_DIFFUSE) { + radiance += throughput * eval_direct_lighting(hit, mat, seed); + } - ScatterResult sc = scatter_pbr(ray, hit, mat, seed); + ScatterResult sc = scatter_ray(ray, hit, mat, seed); if (!sc.scattered) break; throughput *= sc.attenuation; diff --git a/src/core/raytracer.cpp b/src/core/raytracer.cpp index 0b78c45..b056d77 100644 --- a/src/core/raytracer.cpp +++ b/src/core/raytracer.cpp @@ -1,5 +1,4 @@ #include "core/raytracer.h" -#include "scene/pbr_material_gpu.h" #include "basic/constants.h" #include "utils/logger.h" #include @@ -245,32 +244,50 @@ void RayTracer::set_config(const RayTracerConfig &config) { } void RayTracer::upload_scene_data_(const Scene &scene) { - // Upload PBR materials (temporary: texture indices = TEX_INVALID) - const auto& materials = scene.get_materials(); + // Upload materials (on change only) + const auto &materials = scene.get_materials(); if (!materials.empty()) { - std::vector material_data; + struct MaterialData { + Vec3 albedo; + float metallic; + Vec3 emission; + float roughness; + int type; + float ior; + Vec2 padding; + }; + + std::vector material_data; material_data.reserve(materials.size()); - for (const auto& mat : materials) { - PbrMaterialGpu m{}; - m.base_color_factor_ = Vec4(mat->get_albedo(), 1.0f); - m.emissive_factor_ = Vec4(mat->get_emission(), 0.0f); - - // Pack flags into float bits (w) - uint flags = 0u; - m.mr_normal_flags_ = Vec4(mat->get_metallic(), mat->get_roughness(), 1.0f, glm::uintBitsToFloat(flags)); - - m.tex0_ = glm::uvec4(TEX_INVALID, TEX_INVALID, TEX_INVALID, TEX_INVALID); - m.tex1_ = glm::uvec4(TEX_INVALID, TEX_INVALID, TEX_INVALID, TEX_INVALID); - - material_data.push_back(m); + for (const auto &mat : materials) { + MaterialData data {}; + data.albedo = mat->get_albedo(); + data.metallic = mat->get_metallic(); + data.emission = mat->get_emission(); + data.roughness = mat->get_roughness(); + data.type = static_cast(mat->get_type()); + data.ior = mat->get_ior(); + material_data.push_back(data); } - glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_); - glBufferData(GL_SHADER_STORAGE_BUFFER, - material_data.size() * sizeof(PbrMaterialGpu), - material_data.data(), GL_DYNAMIC_DRAW); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_); + uint h = fnv1a_hash_bytes(material_data.data(), material_data.size() * sizeof(MaterialData)); + if (h != materials_hash_) { + materials_hash_ = h; + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_); + glBufferData(GL_SHADER_STORAGE_BUFFER, + material_data.size() * sizeof(MaterialData), + material_data.data(), GL_DYNAMIC_DRAW); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_); + + reset_accumulation(); // materials changed => invalidate accumulation + } else { + // Still ensure bound (in case other code changed bindings) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_); + } + } else { + materials_hash_ = 0u; } // Upload lights (on change only) diff --git a/src/resource/texture_array.cpp b/src/resource/texture_array.cpp deleted file mode 100644 index a7ff0ba..0000000 --- a/src/resource/texture_array.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "resource/texture_array.h" -#include "utils/logger.h" -#include - -namespace are { - -TextureArray::TextureArray() - : handle_(INVALID_HANDLE) - , width_(0) - , height_(0) - , layers_(0) - , internal_format_(0) { -} - -TextureArray::~TextureArray() { - release(); -} - -TextureArray::TextureArray(TextureArray&& other) noexcept - : handle_(other.handle_) - , width_(other.width_) - , height_(other.height_) - , layers_(other.layers_) - , internal_format_(other.internal_format_) { - other.handle_ = INVALID_HANDLE; - other.width_ = 0; - other.height_ = 0; - other.layers_ = 0; - other.internal_format_ = 0; -} - -TextureArray& TextureArray::operator=(TextureArray&& other) noexcept { - if (this == &other) return *this; - release(); - handle_ = other.handle_; - width_ = other.width_; - height_ = other.height_; - layers_ = other.layers_; - internal_format_ = other.internal_format_; - other.handle_ = INVALID_HANDLE; - other.width_ = 0; - other.height_ = 0; - other.layers_ = 0; - other.internal_format_ = 0; - return *this; -} - -bool TextureArray::create(uint width, uint height, uint layers, uint internal_format) { - release(); - - width_ = width; - height_ = height; - layers_ = layers; - internal_format_ = internal_format; - - glGenTextures(1, &handle_); - glBindTexture(GL_TEXTURE_2D_ARRAY, handle_); - - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, internal_format_, width_, height_, layers_); - - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT); - - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - return true; -} - -bool TextureArray::upload_rgba8(uint layer, const void* data, uint width, uint height) { - if (!is_valid()) { - ARE_LOG_ERROR("TextureArray upload on invalid handle"); - return false; - } - if (layer >= layers_) { - ARE_LOG_ERROR("TextureArray layer out of range"); - return false; - } - if (width != width_ || height != height_) { - ARE_LOG_WARN("TextureArray upload size mismatch (resizing not implemented yet)"); - return false; - } - - glBindTexture(GL_TEXTURE_2D_ARRAY, handle_); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, - 0, 0, 0, static_cast(layer), - width_, height_, 1, - GL_RGBA, GL_UNSIGNED_BYTE, data); - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - return true; -} - -void TextureArray::bind(uint unit) const { - glActiveTexture(GL_TEXTURE0 + unit); - glBindTexture(GL_TEXTURE_2D_ARRAY, handle_); -} - -void TextureArray::release() { - if (handle_ != INVALID_HANDLE) { - glDeleteTextures(1, &handle_); - handle_ = INVALID_HANDLE; - } - width_ = height_ = layers_ = 0; - internal_format_ = 0; -} - -} // namespace are