#include "gpu/gpu_types.h" #include "gpu/gpu_renderer.h" #include "log/log.h" #include "math/nmath.h" #include "memory/memory.h" using namespace GS; using namespace GS::GPU; //------------------------------------------------------------------------------ Render::Texture *Renderer::CreateRenderTexture( const char *name, uint width, uint height, Render::Texture::Format format, Render::Texture::AA aa ) { __NTRACE("CreateRenderTexture") // __LOG_W__ << "CreateRenderTexture: name=" << name << ", size=" << width << "x" << height << "\n"; Render::Texture *tex = NewTexture(name); if (!tex) { __LOG_E__ << "CreateRenderTexture: NewTexture failed!\n"; return nullptr; } // __LOG_W__ << "CreateRenderTexture: NewTexture succeeded, tex ptr=" << (void*)tex << "\n"; const Render::Texture::Usage usage = Render::Texture::Usage( Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource ); // __LOG_W__ << "CreateRenderTexture: Calling tex->Create with format=" << format << ", aa=" << aa << "\n"; if (tex->Create(nullptr, width, height, format, aa, usage)) { // __LOG_W__ << "CreateRenderTexture: Success! Returning texture.\n"; return tex; } __LOG_E__ << "CreateRenderTexture: tex->Create failed!\n"; tex->Free(); return nullptr; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Renderer::ClearTexture(Render::Texture *target, const Color &clear_color) { __NTRACE("ClearTexture") // __LOG_W__ << "ClearTexture: Clearing target texture with color (" << clear_color.x << ", " << clear_color.y << ", " << clear_color.z << ", " << clear_color.w << ")\n"; if (!target) { __LOG_E__ << "ClearTexture: target is null!\n"; return; } const uint tex_w = target->GetWidth(); const uint tex_h = target->GetHeight(); // Use DrawSplineToTexture with clear_target=true to clear the texture // This is a workaround since Blit() doesn't work on RenderTarget textures // __LOG_W__ << "ClearTexture: Using DrawSplineToTexture method with clear...\n"; // Create a dummy 2-point array (not used since we're clearing) Array dummy_points; dummy_points.Allocate(2); dummy_points[0] = Vector2(0, 0); dummy_points[1] = Vector2(1, 1); // Call DrawSplineToTexture with clear_target=true and zero width (no drawing) // This will fill the buffer with the clear color const uint pitch = tex_w * 4; const size_t buffer_size = pitch * tex_h; unsigned char *pixels = new unsigned char[buffer_size]; // Convert color to 8-bit RGBA const auto r = (unsigned char)(clear_color.x * 255.0f); const auto g = (unsigned char)(clear_color.y * 255.0f); const auto b = (unsigned char)(clear_color.z * 255.0f); const auto a = (unsigned char)(clear_color.w * 255.0f); // __LOG_W__ << "ClearTexture: Filling buffer with RGBA(" << (int)r << ", " << (int)g << ", " << (int)b << ", " << (int)a << ")...\n"; // Fill with clear color (ABGR format) unsigned int pixel_value = (a << 24) | (b << 16) | (g << 8) | r; unsigned int *pixel_buffer = (unsigned int *)pixels; for (uint i = 0; i < tex_w * tex_h; ++i) { pixel_buffer[i] = pixel_value; } // __LOG_W__ << "ClearTexture: Uploading via Blit...\n"; target->Blit((const char *)pixels, tex_w, tex_h, 0, 0, Render::Texture::FormatRGBA8); delete[] pixels; // __LOG_W__ << "ClearTexture: Complete!\n"; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Renderer::BlitTextureToTexture( Render::Texture *src, Render::Texture *dst, const fRect *src_rect, const fRect *dst_rect ) { __NTRACE("BlitTextureToTexture") if (!src || !dst) return; // Default rectangles (full texture) const fRect src_r = src_rect ? *src_rect : fRect(0, 0, 1, 1); const fRect dst_r = dst_rect ? *dst_rect : fRect(0, 0, 1, 1); // Save current state const fRect old_viewport = viewport; // Setup FBO for destination fx_fbo->SetColorTexture(dst); fx_fbo->SetDepthTexture(nullptr); SetCurrentFBO(fx_fbo); SetViewport(fRect(0, 0, (float)dst->GetWidth(), (float)dst->GetHeight())); // Use existing RenderFullscreenQuad infrastructure RenderFullscreenQuad(*single_texture_program, src_r, dst_r, src); // Restore state SetCurrentFBO(nullptr); SetViewport(old_viewport); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Vector2 Renderer::CatmullRomInterpolate( const Vector2 &p0, const Vector2 &p1, const Vector2 &p2, const Vector2 &p3, float t ) const { // Catmull-Rom spline with tau = 0.5 const float t2 = t * t; const float t3 = t2 * t; // Manual calculation for Vector2 Vector2 result; result.x = 0.5f * ( (2.0f * p1.x) + (-p0.x + p2.x) * t + (2.0f * p0.x - 5.0f * p1.x + 4.0f * p2.x - p3.x) * t2 + (-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t3 ); result.y = 0.5f * ( (2.0f * p1.y) + (-p0.y + p2.y) * t + (2.0f * p0.y - 5.0f * p1.y + 4.0f * p2.y - p3.y) * t2 + (-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t3 ); return result; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Renderer::TessellateSplineRibbon( const Array &control_points, float width_normalized, const Color &color, int samples_per_segment, bool soft_edge, SplineRibbon &out_ribbon ) { __NTRACE("TessellateSplineRibbon") const int num_control = control_points.GetCount(); if (num_control < 2) return; // For Catmull-Rom: we have num_control-1 segments const int num_segments = num_control - 1; if (num_segments < 1) return; // Allocate arrays for sampled points const int total_samples = num_segments * samples_per_segment + 1; Array samples; Array tangents; samples.Allocate(total_samples); tangents.Allocate(total_samples); // Sample the spline using Catmull-Rom interpolation int sample_idx = 0; for (int seg = 0; seg < num_segments; seg++) { // Get 4 control points (with duplication at boundaries) Vector2 p0 = (seg > 0) ? control_points[seg - 1] : control_points[seg]; Vector2 p1 = control_points[seg]; Vector2 p2 = control_points[seg + 1]; Vector2 p3 = (seg < num_segments - 1) ? control_points[seg + 2] : control_points[seg + 1]; for (int s = 0; s < samples_per_segment; s++) { const float t = (float)s / (float)samples_per_segment; samples[sample_idx] = CatmullRomInterpolate(p0, p1, p2, p3, t); // Compute tangent (derivative approximation) const float dt = 0.01f; const float t_next = (t + dt > 1.0f) ? 1.0f : t + dt; Vector2 p_next = CatmullRomInterpolate(p0, p1, p2, p3, t_next); Vector2 diff = p_next - samples[sample_idx]; tangents[sample_idx] = diff.Normalized(); sample_idx++; } } // Add final point samples[total_samples - 1] = control_points[num_control - 1]; tangents[total_samples - 1] = tangents[total_samples - 2]; // Reuse last tangent // Build ribbon geometry out_ribbon.positions.Allocate(total_samples * 2); out_ribbon.colors.Allocate(total_samples * 2); for (int i = 0; i < total_samples; i++) { const Vector2 pos = samples[i]; const Vector2 tangent = tangents[i]; // Perpendicular normal (2D rotation by 90 degrees) Vector2 normal(-tangent.y, tangent.x); // Convert from normalized [0..1] texture space to clip space [-1..1] const float x_clip = pos.x * 2.0f - 1.0f; const float y_clip = 1.0f - pos.y * 2.0f; // Flip Y (texture origin top-left) // Offset in clip space const Vector2 offset = normal * width_normalized; // Left and right vertices out_ribbon.positions[i * 2 + 0] = Vector4( x_clip - offset.x, y_clip - offset.y, 0.0f, 1.0f ); out_ribbon.positions[i * 2 + 1] = Vector4( x_clip + offset.x, y_clip + offset.y, 0.0f, 1.0f ); // Colors with soft edge via alpha gradient if (soft_edge) { // Apply alpha gradient on outer edges (30% opacity) vs center (full opacity) // But since we're using a ribbon with 2 vertices per sample, both get same alpha // To get true soft edge, we'd need a center vertex too, but for now just use the color as-is // TODO: Implement true soft edge with centerline vertex if needed out_ribbon.colors[i * 2 + 0] = color; out_ribbon.colors[i * 2 + 1] = color; } else { out_ribbon.colors[i * 2 + 0] = color; out_ribbon.colors[i * 2 + 1] = color; } } // Build indices (triangle list) const int num_quads = total_samples - 1; out_ribbon.indices.Allocate(num_quads * 6); for (int i = 0; i < num_quads; i++) { const ushort i0 = (ushort)(i * 2); const ushort i1 = (ushort)(i * 2 + 1); const ushort i2 = (ushort)((i + 1) * 2); const ushort i3 = (ushort)((i + 1) * 2 + 1); // Triangle 1 out_ribbon.indices[i * 6 + 0] = i0; out_ribbon.indices[i * 6 + 1] = i2; out_ribbon.indices[i * 6 + 2] = i1; // Triangle 2 out_ribbon.indices[i * 6 + 3] = i1; out_ribbon.indices[i * 6 + 4] = i2; out_ribbon.indices[i * 6 + 5] = i3; } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // Helper: Draw a simple line to texture pixel data (Bresenham-like) static void DrawLineToPixels(unsigned char *pixels, uint width, uint height, uint pitch, int x0, int y0, int x1, int y1, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { const int dx = abs(x1 - x0); const int dy = abs(y1 - y0); const int sx = (x0 < x1) ? 1 : -1; const int sy = (y0 < y1) ? 1 : -1; int err = dx - dy; int x = x0, y = y0; while (true) { if (x >= 0 && x < (int)width && y >= 0 && y < (int)height) { int *pixel = (int*)(pixels + y * pitch + x * 4); // Read destination pixel components (packed as in existing code: ABGR) const unsigned int dst = *pixel; const unsigned char dst_r = (unsigned char)(dst & 0xFF); const unsigned char dst_g = (unsigned char)((dst >> 8) & 0xFF); const unsigned char dst_b = (unsigned char)((dst >> 16) & 0xFF); const unsigned char dst_a = (unsigned char)((dst >> 24) & 0xFF); // Source alpha (0..1) const float src_af = (float)a / 255.0f; const float inv = 1.0f - src_af; // Standard 'over' compositing: out = src*src_a + dst*(1-src_a) const unsigned char out_r = (unsigned char)(r * src_af + dst_r * inv + 0.5f); const unsigned char out_g = (unsigned char)(g * src_af + dst_g * inv + 0.5f); const unsigned char out_b = (unsigned char)(b * src_af + dst_b * inv + 0.5f); const unsigned char out_a = (unsigned char)(a + dst_a * inv + 0.5f); *pixel = (out_a << 24) | (out_b << 16) | (out_g << 8) | out_r; // ABGR packing (kept as original) } if (x == x1 && y == y1) break; const int e2 = 2 * err; if (e2 > -dy) { err -= dy; x += sx; } if (e2 < dx) { err += dx; y += sy; } } } void Renderer::DrawSplineToTexture( Render::Texture *target, const Array &control_points, float width_pixels, const Color &color, int samples_per_segment, bool clear_target, bool soft_edge, const Color *border_color, float border_width, float margin ) { __NTRACE("DrawSplineToTexture") // __LOG_W__ << "Drawing spline to texture (CPU-based method)...\n"; if (!target) { __LOG_E__ << "DrawSplineToTexture: target texture is null!\n"; return; } if (control_points.GetCount() < 2) { __LOG_E__ << "DrawSplineToTexture: not enough control points!\n"; return; } const uint tex_w = target->GetWidth(); const uint tex_h = target->GetHeight(); // __LOG_W__ << "Target texture size: " << tex_w << "x" << tex_h << "\n"; // Convert color to 8-bit RGBA const auto r = (unsigned char)(color.x * 255.0f); const auto g = (unsigned char)(color.y * 255.0f); const auto b = (unsigned char)(color.z * 255.0f); const auto a = (unsigned char)(color.w * 255.0f); // __LOG_W__ << "Color: R=" << (int)r << " G=" << (int)g << " B=" << (int)b << " A=" << (int)a << "\n"; // Convert border color if provided unsigned char border_r = 0, border_g = 0, border_b = 0, border_a = 0; if (border_color && border_width > 0.0f) { border_r = (unsigned char)(border_color->x * 255.0f); border_g = (unsigned char)(border_color->y * 255.0f); border_b = (unsigned char)(border_color->z * 255.0f); border_a = (unsigned char)(border_color->w * 255.0f); // __LOG_W__ << "Border Color: R=" << (int)border_r << " G=" << (int)border_g << " B=" << (int)border_b << " A=" << (int)border_a << " Width=" << border_width << "\n"; } const int num_control = control_points.GetCount(); const int num_segments = num_control - 1; // Adjust control points with margin to avoid clipping Array adjusted_points; adjusted_points.Allocate(num_control); if (margin > 0.0f) { // __LOG_W__ << "Applying margin: " << margin << "\n"; // Calculate the margin in normalized coordinates [0..1] const float margin_x = margin / (float)tex_w; const float margin_y = margin / (float)tex_h; // Scale and offset control points to fit within margin bounds float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; for (int i = 0; i < num_control; i++) { if (control_points[i].x < min_x) min_x = control_points[i].x; if (control_points[i].x > max_x) max_x = control_points[i].x; if (control_points[i].y < min_y) min_y = control_points[i].y; if (control_points[i].y > max_y) max_y = control_points[i].y; } const float range_x = max_x - min_x; const float range_y = max_y - min_y; const float scale_x = (range_x > 0.0f) ? (1.0f - 2.0f * margin_x) / range_x : 1.0f; const float scale_y = (range_y > 0.0f) ? (1.0f - 2.0f * margin_y) / range_y : 1.0f; for (int i = 0; i < num_control; i++) { adjusted_points[i].x = margin_x + (control_points[i].x - min_x) * scale_x; adjusted_points[i].y = margin_y + (control_points[i].y - min_y) * scale_y; } } else { // No margin, use original points for (int i = 0; i < num_control; i++) { adjusted_points[i] = control_points[i]; } } Array samples; samples.Allocate(num_segments * samples_per_segment + 1); // Sample the spline using Catmull-Rom interpolation with adjusted points int sample_idx = 0; for (int seg = 0; seg < num_segments; seg++) { Vector2 p0 = (seg > 0) ? adjusted_points[seg - 1] : adjusted_points[seg]; Vector2 p1 = adjusted_points[seg]; Vector2 p2 = adjusted_points[seg + 1]; Vector2 p3 = (seg < num_segments - 1) ? adjusted_points[seg + 2] : adjusted_points[seg + 1]; for (int s = 0; s < samples_per_segment; s++) { const float t = (float)s / (float)samples_per_segment; samples[sample_idx] = CatmullRomInterpolate(p0, p1, p2, p3, t); sample_idx++; } } samples[num_segments * samples_per_segment] = adjusted_points[num_control - 1]; // __LOG_W__ << "Generated " << samples.GetCount() << " sampled points\n"; // Create pixel buffer for drawing // __LOG_W__ << "Creating pixel buffer for CPU rendering...\n"; const uint pitch = tex_w * 4; // RGBA = 4 bytes per pixel const size_t buffer_size = pitch * tex_h; unsigned char *pixels = new unsigned char[buffer_size]; // __LOG_W__ << "Pixel buffer created. Size=" << buffer_size << " Pitch=" << pitch << "\n"; if (clear_target) { // Initialize with transparent black memset(pixels, 0, buffer_size); } else { // Read back the existing texture contents into the buffer so we can composite on top. // __LOG_W__ << "Reading existing texture pixels into buffer (for compositing)...\n"; // Setup FBO for readback fx_fbo->SetColorTexture(target); fx_fbo->SetDepthTexture(nullptr); SetCurrentFBO(fx_fbo); fx_fbo->ReadColorPixels((char*)pixels, 0, 0, tex_w, tex_h); // Restore SetCurrentFBO(nullptr); } // __LOG_W__ << "Drawing spline lines...\n"; // Draw border first (if specified) if (border_color && border_width > 0.0f) { // __LOG_W__ << "Drawing border with width: " << border_width << "\n"; const float total_width = width_pixels + border_width * 2.0f; for (uint i = 0; i + 1 < samples.GetCount(); i++) { const int x0 = (int)(samples[i].x * (float)tex_w); const int y0 = (int)(samples[i].y * (float)tex_h); const int x1 = (int)(samples[i + 1].x * (float)tex_w); const int y1 = (int)(samples[i + 1].y * (float)tex_h); for (int w = -(int)total_width/2; w <= (int)total_width/2; w++) { DrawLineToPixels(pixels, tex_w, tex_h, pitch, x0 + w, y0, x1 + w, y1, border_r, border_g, border_b, border_a); } } } // Draw main spline on top for (uint i = 0; i + 1 < samples.GetCount(); i++) { // Convert from normalized [0..1] to pixel coordinates const int x0 = (int)(samples[i].x * (float)tex_w); const int y0 = (int)(samples[i].y * (float)tex_h); const int x1 = (int)(samples[i + 1].x * (float)tex_w); const int y1 = (int)(samples[i + 1].y * (float)tex_h); // Draw line with width by drawing multiple parallel lines for (int w = -(int)width_pixels/2; w <= (int)width_pixels/2; w++) { DrawLineToPixels(pixels, tex_w, tex_h, pitch, x0 + w, y0, x1 + w, y1, r, g, b, a); } } // __LOG_W__ << "Uploading pixel buffer to texture...\n"; // Upload to texture using Blit target->Blit((const char *)pixels, tex_w, tex_h, 0, 0, Render::Texture::FormatRGBA8); // __LOG_W__ << "Freeing pixel buffer...\n"; delete[] pixels; // __LOG_W__ << "Buffer freed!\n"; // __LOG_W__ << "DrawSplineToTexture complete!\n"; } //------------------------------------------------------------------------------