Optimizing a render pipeline without losing visual craft is a high-wire act. You call to hit frame rate targets on modest hardware, but the game or app must still look good enough to impress. So how do you choose which optimizations to apply without wrecking the visuals? This article lays out a repeatable method: who this matters for, what goes faulty when you skip it, the exact steps to follow, and the pitfalls that trip up most units.
Who Needs This and What Goes faulty Without It
According to internal training notes, beginners fail when they tune for shortcuts before they fix the baseline.
Identifying the audience: game developers, VFX artists, real-slot visualization engineers
You ship experiences for screens—games, architectural walkthroughs, VR training tools, maybe a virtual offering configurator. Your job is to produce it run at 60 fps (or 90, or 144) without the image looking like a watercolor painting left in the rain. I have sat with crews who thought 'optimization' meant cutting resoluing until the UI text blurred. That is not optimization. That is surrender. If you are a technical artist wrestling a shader-heavy scene, an indie dev pushing one more particle framework into an already-crowded frame, or an engineer trying to squeeze a 4K real-estate tour onto a mid-tier laptop—this is for you.
usual failures: blind resolu cuts, disabling effects, pipeline switch without testing
The most frequent mistake is reaching for the low-hanging fruit and yanking the branch off the tree. resolual goes from 100% to 75%—textures soften, aliasing vanishes for a moment, but the frame window barely drops. Next comes the culling spree: shadows to low, reflections off, bloom killed. That hurts. The visual mood collapses, yet the frame budget still hovers at 33 ms. Worse is the pipeline swap—jumping from Forward to Deferred, or tossing in FSR 2 without profiling, hoping it just works. It does not just task. The catch is that switching render paths changes how depth, transparency, and MSAA behave. Objects that looked crisp turn into shimmering ghosts. You lose a day debugging why the water shader explodes on mobile.
What usually breaks opening is the lighting. Or the transparency sorting. Or both at different times, so the crew scatters fixes across a week.
One concrete anecdote: I watched a tight studio drop their average frame rate by 12 fps by turning off post-processing globally. Then they spent two weeks rebuilding a fog setup from scratch because the art director hated the 'naked' look. They could have profiled the fog pass alone—fixed it in an afternoon. That is the spend of no systematic tactic: visual regressions pile up, wasted phase compounds, and the frame rate target stays missed.
Optimization without profiling is just guessing with someone else's frame rate.
— Overheard at a GDC roundtable, 2023
The spend of no systematic tactic: visual regressions, wasted slot, missed frame rate targets
When you treat optimization as a reactive scramble—throw one toggle, measure nothing, repeat—you get regressions that look like bugs. A shadow map drops to 512? Suddenly foliage casts blocky blobs. Disable SSAO to save 2ms? Indoor scenes turn flat, and the lighting artist rebels. The real spend is not just visual: it is structural. Art assets get rebuilt to compensate for a bad pipeline choice. I have seen crews delete entire particle systems because the draw-call batching was never tuned. They blamed the particles. The particles were innocent. The renderer was just set up off.
Missed frame rate targets hurt twice. primary, you ship late, patching after launch. Second, the players or clients notice within minutes—stutter, drop-outs, that spinning circle on transition. There is no second initial impression for lag.
faulty sequence is the silent budget killer. Most crews skip the shift where you ask: 'What is the actual constraint here—GPU compute, fill rate, draw calls, or memory bandwidth?' Without that answer, every optimization is a dice roll. Roll badly enough, and you end up with a scene that runs at 45 fps but looks like it came from 2012. That is not a trade-off. That is a loss on both sides.
Prerequisites: What You Should Settle opening
Profiling Tools: Know What You're Measuring Before You Touch a Shader
You cannot tune what you cannot see. That sounds obvious, but I have watched crews rewrite entire lighting pipelines only to discover they were bottlenecked on triangle setup — not shading at all. Pick your profiler before you open a shader graph. RenderDoc is non-negotiable for frame captures; it gives you raw draw-call timings, shader instruction counts, and buffer readbacks without engine abstraction. Unity's Frame Debugger or Unreal's GPU Visualizer will show you the spend per pass — use them. If your platform is mobile, pay attention to the tile-based GPU traces (Qualcomm's Adreno Profiler, Apple's Metal Debugger). Their counters expose a different beast: overdraw kills you, not raw fill rate. The catch? Many units jump straight into tweaking texture sizes or LOD bias, skipping the trace phase entirely. They shave milliseconds from the CPU side while the GPU is still drowning in wasted pixels. Don't be that group.
What about CPU-side profiling? Grab a thread-tracing instrument — the built-in profiler in your engine often works, but I prefer Superluminal or Tracy for real-window Windows captures. These will reveal the silent killers: script overhead, animation update storms, excessive GetComponent calls. One concrete anecdote: a studio I worked with spent two weeks optimizing shadow cascades only to find a one-off C# loop re-evaluating renderer visibility every frame. That loop overhead 8ms. We killed it, gained 12 FPS, and the shadow effort was always fine. Know your aid. Then trust its numbers over your gut.
Understanding Your constraint: CPU vs. GPU, Fill Rate vs. Draw Calls
Fix the faulty limiter and you actually craft performance worse. Here is the reality: reducing draw calls when you are GPU-limited adds overhead (more batching effort) with zero frame-phase payoff. So which kind of limit are you hitting? rapid litmus probe — if the profiler shows less than 16ms of GPU slot but your game thread spikes to 20ms, you are CPU-bound. Conversely, if the game thread sleeps while the GPU render loop churns past your target, you are GPU-bound. The distinction matters because the levers differ wildly. For CPU bottlenecks: cut draw calls via instancing, dirty-rect culling, or ECS logic offload. For GPU bottlenecks: lower shader instruction counts, lower resolual targets, cull pixel overdraw, or compress textures to reduce bandwidth pressure. One frequent pitfall: confusing fill-rate issues with memory bandwidth problems. They smell similar — blurred resolu, frame-window jitter — but one needs smaller render targets (fill rate) and the other needs BC/ETC compression or mip adjustments (bandwidth). Profile the specific counter before hammering.
We thought we had a draw-call snag. Turned out we just had eighteen translucent particle systems overlapping on a solo screen pixel.
— Lead engineer, mobile title, after primary trace
That quote nails it. Translucency kills fill rate faster than any shader complexity. If your scene has layered overlapping transparent objects, you might be pixel-bound even with trivial shaders. Check the overdraw visualization in RenderDoc (the heatmap gradient from blue to red). If the red areas span more than 10% of the screen, you have a different issue than draw calls — no amount of batching fixes it.
Baseline Metrics: Frame phase, Memory Budget, resolual Targets
Write these down before you adjustment anything. I mean literally — a text file or a spreadsheet row. Frame slot target (e.g., 16.67ms for 60 FPS, 33.33ms for 30 FPS), peak memory budget (split by CPU heap and GPU VRAM), and your rendering resoluing target (could be native 1920x1080 or something like 70% volume with upscaling). Without these numbers, you have no way to declare victory or defeat. Example: Suppose you drop texture standard and shave 50MB from VRAM. That sounds fine until you realize your budget was 2GB and you were never close to the limit anyway — you gained nothing. Worst case, you reduced visual craft for zero performance uplift. Set the bar initial. Then tune until you hit it. Once you cross it, stop. Over-optimization introduces artifacts, increases iteration window, and often invalidates previous effort (like detail textures that suddenly fight with aggressive mip bias). Save your sanity — baseline opening.
One more thing you should settle: your worst-case scene. Not your hero gallery shot. Pick the level where frame phase spikes, particles flood, shadows cascade — whatever causes pain. Profile that scene as your primary baseline. If you sharpen the sunny meadow while the underground fire boss fight still chokes at 18 FPS, you have shipped a broken product. The catch is human nature — people avoid profiling their ugliest levels because it hurts to see the numbers. Force it. I have seen entire release cycles saved because a producer finally pushed the hot scene into the profiler on week eight instead of week fourteen.
So: profiler installed, chokepoint identified, metrics written down, worst-case scene locked. You are now ready to tune without guesswork. Next step is the actual sequence — what to tweak primary, what protects visual fidelity, and where the traps hide. That comes in the Core routine segment.
Core routine: Sequential Steps to sharpen Without Sacrifice
A site lead says crews that document the failure mode before retesting cut repeat errors roughly in half.
phase 1: Profile and identify waste
Stop optimizing before you know what is actually slow. I have watched crews spend two weeks rewriting a water shader only to discover the real chokepoint was 12,000 draw calls from a one-off forgotten particle setup. Open the GPU profiler initial — RenderDoc, Xcode Frame Debugger, or the Unity Frame Debugger will do. Look for the red bars. That is where your budget leaks. The catch is that most people chase triangles when they should chase overdraw. A simple plane with a transparent layered shader can spend more than a high-poly rock. Profile in the worst-case camera position, not your hero shot. Then list every offender.
shift 2: Apply targeted optimizations
Once the data is in hand, effort from biggest gain to smallest risk. Level-of-detail systems belong on the three most expensive meshes, not everything. Culling — frustum, occlusion, and distance — often reclaims 30–40% of frame slot without touching a one-off texture. Shader complexity is the hidden trap: a solo branch instruction can force a warp divergence on the GPU. Swap it out. Use material property blocks instead of unique materials. off sequence here hurts — baking LODs for a rock that appears once in the distance while leaving a hair system with 400 submeshes untouched is a common mistake. Prioritize by measured expense, not fear.
The seam between 'optimized' and 'ugly' is thinner than most devs admit. You find it by diffing screenshots, not hoping.
— QA lead on a shipped open-world title, after chasing a half-frame regression for three days
shift 3: check visual parity
This is the phase everyone skips until something breaks. Frame-by-frame screenshot comparison tools — I use RenderDoc automated capture or a diff script with ImageMagick — catch subtle shifts in shadow bias or LOD pop that human eyes miss during a rapid playthrough. Set a tolerance threshold. Green means go. If the silhouette or ambient occlusion band shifts by more than two pixels, revert the shift. The trick: trial on a construct without a debug overlay. Performance gains mean nothing if the game looks flawed in a press screenshot. We fixed a flickering grass issue once by realizing the LOD transition distance was too tight — the fix was a 0.2-second crossfade, not a revert.
phase 4: Iterate and measure
Optimization is a loop, not a chain. Apply one revision, measure again, compare the before and after profiler captures. Did draw calls drop but frame window stay flat? Probably a CPU-side stall — check script updates or animation blending. Did GPU phase drop but memory spike? That dynamic batching might be instantiating mesh data on the heap. Each cycle should tighten the gap between current performance and your target budget. Three passes is normal. After that, if the numbers still stink, something deeper is flawed — maybe the art style assumes more draw calls than mobile can stomach. No shame in adjusting the brief. The next section covers the tools that craft this cycle tolerable.
Tools and Environment Realities
RenderDoc for frame capture and shader debugging
The primary aid I reach for after any visual regression isn't the engine profiler — it's RenderDoc. Free, cross-platform, and ruthlessly honest. You grab a one-off frame, transition through every draw call, inspect every texture bound. Why guess at bandwidth when you can see the exact 128 MB of unused mip data streaming to VRAM? The catch is learning to read the Event Browser without panicking. Most crews skip the 'Mesh Output' tab, then wonder why their shadow cascades spend 2.3 ms. Don't.
RenderDoc exposes one painful truth: shader complexity often hides in plain sight. I once found a surface shader recompiling for every light count variation — the capture showed fifty similar draws with identical inputs. That one-off fix reclaimed 4 ms on mobile. The instrument doesn't fix your pipeline; it shows you where your assumptions about the pipeline are faulty. Hard to argue with a frame capture.
GPU vendor-specific tools: NVIDIA Nsight, AMD Radeon GPU Profiler
The engine lies to you. Not maliciously — it just optimizes for features, not for the metal. NVIDIA Nsight Graphics gives per-draw-call warp occupancy and memory stall reasons. AMD's Radeon GPU Profiler (RGP) does the same for the other half of the desktop market. The trade-off is brutal: you call at least two GPUs on your testing bench if you target both ecosystems. One project I consulted on optimized solely for Nsight metrics — lovely graphs, terrible frame times on AMD hardware three weeks before ship. They had to revert two texture-packing changes.
RGP's pipeline stall view is worth the install hassle alone. It shows exactly where the shader unit starves: waiting on vertex data, texture fetches, or export back-pressure. Most desktop devs never see this data. That hurts. For mobile, switch to Qualcomm's Snapdragon Profiler or Mali's Graphics Analyzer — they expose tile-buffer limits that desktop profilers abstract away. Different hardware, different lies.
Engine profilers: Unity Frame Debugger, Unreal GPU Visualizer
swift reality check—engine tools are for triage, not diagnosis. Unity's Frame Debugger tells you what rendered, not why it took 8 ms. Unreal's GPU Visualizer splits passes neatly, but its timing data aggregates across multiple frames. One spike disappears into the average. You lose a day chasing a phantom. I still use them opening because they catch the obvious: a translucent object drawing after opaque pass, a shadow cascade that never culls. But never stop at the engine tool. It's the starting chain, not the finish.
We fixed this by treating engine profilers as smoke detectors, not autopsies. Unity shows you overdraw. Unreal shows you shader permutation counts. Both are symptoms. The underlying cause — why the material has forty variants — requires RenderDoc or Nsight to unwrap. Different tools, different depths. Use them in that sequence.
A profiler that doesn't show you where the pipeline stalls is just a pretty histogram.
— Senior rendering engineer, after three weeks debugging a texture-streaming bug that RGP found in forty minutes
Hardware realities: mobile SoCs vs. desktop GPUs
Desktop GPUs are forgiving. They have bandwidth to burn. Mobile SoCs? Not so much. The same draw call that runs at 0.3 ms on a GeForce RTX can take 3 ms on a Snapdragon 8 Gen 2 — if it causes a tile-buffer overflow. The difference isn't shader complexity alone; it's memory bandwidth contention between the GPU and the display controller, the modem, the camera ISP. You can't profile for that on a development PC.
Hardest lesson I learned: always probe on the lowest thermal-throttled device your users actually own. An iPhone 12 throttles differently than a Pixel 7. A Samsung S22 overheats faster in summer. These are not QA edge-cases; they are the reality of mobile rendering. Set up your profiler to log sustained frame times — not just the initial thirty seconds of a scene. That's where the pipeline breaks, and no desktop simulation will replicate it.
One final blunt recommendation: buy an Odroid or a cheap Android tablet with your target SoC. Wire it to a power watch. Measure milliamps alongside milliseconds. The ratio tells you whether your optimization actually saves battery or just shifts task elsewhere. Most of the slot, it shifts. Check that.
Variations for Different Constraints
According to published workflow guidance, skipping the calibration log is the pitfall that shows up on audit day.
Mobile optimization: fill rate limits, bandwidth, resolual scaling
Mobile GPUs live under a tight thermal ceiling. What usually breaks primary is bandwidth—memory clock speeds can't retain up with even medium-res screens. I have seen units ship a gorgeous deferred renderer only to watch it hit 12 FPS on a Snapdragon chip. The fix isn't pretty: tile-based architecture rewards early-Z rejection and shader complexity kills. Go for forward rendering with a handful of lights pre-baked into light probes. Use resolual scaling dynamically—drop to 70% when the device temperatures spike. That hurts less than losing shadow cascades. Also disable trilinear filtering on distant textures; the visual difference is negligible at phone distance. The hardest part is telling your art director that quarter-res bloom works fine.
Punch the pixel count, not the technique.
Desktop high-end: maximizing visual finish within window budget
High-end desktop has the opposite headache: you can run everything, but frame phase budgets still enforce a hard cap at 16 ms for 60 FPS (8.3 ms for 120 Hz). Where do you spend that budget? I'd rather push temporal upsampling (DLSS/FSR) and keep full screen-zone reflections than waste cycles on draw-call optimization that doesn't transition the needle. The trick is profiling early—most groups assume shadow resoluing is the constraint; actually, post-processing passes steal more phase. Sacrifice one unnecessary blur convolution to maintain cascaded shadow maps at high distance. One reality check: volumetric fog can swallow 3 ms on modern GPUs. Turn it off for non-story scenes, or bake it into a precomputed probe. The catch—your frame rate spikes, but your scene looks identical. Nobody notices.
VR/AR: dual rendering, solo-pass instancing, fixed foveated rendering
VR doubles the issue—literally. Two viewports, two sets of culling, two rasterizations. one-off-pass instancing is not optional; it merges both views into one draw call and cuts CPU overhead in half. Without it, your VR title chokes before the main menu loads. Fixed foveated rendering helps the GPU concentrate samples where the user actually looks—peripheral resolu can drop to 25% of center. I worked on a demo where skipping this move meant missing 72 Hz by 4 ms. We fixed it by adding a dedicated LOD bias for peripheral meshes. The trade-off: you see a slight shimmer at the gaze boundary during fast head rotation. Acceptable. Don't accept broken reprojection.
Film/archviz: offline vs. real-phase, ray tracing hybrid
Film and archviz operate under looser clocks—one frame can cook for minutes. Real-phase previews, though, still demand interactivity. Hybrid path tracing works best: brute-force GI bakes into irradiance volumes for real-phase bounce, then final frames kick out to a denoised path tracer. The pitfall I see is units over-investing in real-slot raytracing during iteration—suddenly the viewport lags, and the client thinks the whole assemble is broken. Use a fat lightmap for the interactive walkthrough and switch to ray traced reflection only for the final export. That switch costs you a static lighting pass but buys you 60 FPS in review sessions. Tight deadlines forgive many sins; a lagging viewport forgives none.
The difference between a production-ready pipeline and a prototype is how gracefully it fails under constrained hardware.
— Technical director, after watching a mobile assemble overheat on the second scene
Pitfalls and What to Check When It Fails
Over-optimizing early: premature optimization vs. measured chokepoint
The most expensive mistake I see units make is rebuilding their entire lighting pipeline two weeks into development—before they have a one-off frame that stutters. You shave 2ms off a shadow pass that was never the problem, and suddenly your beautiful subsurface scattering looks like wax. The trap feels logical: streamline everything now, guarantee headroom later. But you don't know which knob to turn until you've seen the profiler red-series on something specific. Target one metric. One. Run the scene you actually ship, not the demo room with three cubes. That sounds obvious. I have watched three separate studios burn weeks on instancing fixes when their real constraint was a solo post-approach pass set to full resolu.
We saved 40 draw calls on static meshes. The frame still dropped to 18 FPS. We had forgotten to check the SSAO resolution.
— A rebuild that fixed nothing, until the actual culprit was found
Pro tip: profile at your target resolution and with your target LOD bias. A 1080p run hides memory bandwidth issues that blow up at 4K. Measure opening, second-guess your measurement, then sharpen.
Ignoring draw call batching: dynamic batching vs. instancing
Dynamic batching sounds like a free lunch—Unity or Unreal will merge your tight meshes at runtime, no setup needed. The catch: it has strict vertex limits (typically 300 or fewer), it eats CPU slot on the merge itself, and it quietly breaks when your mesh has lightmaps or skinned bones. I have seen a group enable dynamic batching, see their draw calls drop from 800 to 200, and declare victory—only to notice their frame phase stayed flat. The batching expense swallowed the savings. That hurts. Instancing, by contrast, requires you to manually group identical meshes and feed per-instance data (position, color, scale) through a constant buffer. It is more work. But instancing does not collapse under its own bookkeeping. If your scene has fifty identical barrels, instancing wins. If you have fifty unique chairs, neither batching method saves you—you call to merge them into a one-off static mesh at construct window, or eat the draw calls. That is a trade-off most tutorials skip.
Post-processing spend: bloom, DOF, SSAO turned on without checking
Post-processing is where visual fidelity tips into a slideshow. Bloom alone can eat 1.5ms if your blur kernel is set to 64 samples—on mobile that is your entire budget. Depth of field forces a separate render target and a blur pass that scales linearly with resolution. SSAO looks subtle but runs a per-pixel ray march; at 4K with 16 samples, it can expense more than your main lighting pass. The easy fix is to run each effect on a quarter-resolution buffer, but that introduces shimmer and edge artifacts. That is a real visual regression. Most crews skip this: check the post-method volume for default enabled effects. I once found ambient occlusion turned on in a scene that was entirely flat walls—zero occlusion benefit, full performance cost. Turn it off. Then capture a reference frame and toggle effects one by one. Compare. The eye lies. The profiler does not.
Visual regression testing: comparison tools, reference captures
You optimized. The frame phase dropped. The scene looks… off. Shadows feel darker. The bloom halo shrunk. You cannot prove it without a before-and-after. That is where visual regression testing saves your weekend. Tools like RenderDoc let you capture a solo frame, then replay it with different settings—side by side, pixel-difference overlay included. Unreal's Screenshot Comparison utility does the same, flagging any pixel that deviates beyond a tolerance. I set up a folder of reference captures after every meaningful optimization pass. Run the same camera angle, same slot of day, same post-process profile. If the diff image shows red specks across the whole screen, your optimization introduced a systematic error—probably a precision drop in a tone-mapping step. If the diff is isolated to one object, check its material. Did you override its shader variant? Fix that. Then re-run. No guesswork. That is the difference between shipping a assemble that looks identical to your art director's approval shot and shipping a build that gets a 'something feels flawed' email at 2 AM.
FAQ and Prose Checklist
According to internal training notes, beginners fail when they streamline for shortcuts before they fix the baseline.
Should I switch to Forward+ rendering?
Not automatically. Forward+ is brilliant when you have dozens of dynamic lights covering the same screen space—think neon-drenched night clubs or sprawling sci-fi corridors. But I have seen units migrate to Forward+ for a quiet diorama with three static lights, then wonder why their fill rate collapsed. The trade-off is memory bandwidth: Forward+ builds a light grid per tile, and if your scene is mostly shadowed or uses few lights, that grid overhead eats your gains. Profile primary, not hunch-initial. Quick reality check—run your scene with a solo directional light and a few point lights. If your GPU window is already below 8ms, switching pipelines solves nothing. The catch is that Forward+ also complicates MSAA and transparency sorting. If you rely heavily on alpha-blended foliage or glass, probe before you commit. One team I worked with cut draw calls by 40% but introduced a seam of flickering translucency that took two weeks to untangle.
How to trial visual fidelity objectively?
Don't trust your eyes alone—we bias toward our own changes. Capture a reference frame before any optimization, ideally a raw EXR screenshot or a lossless PNG sequence. Then compare using a difference metric: pixel-level PSNR is fine for motion graphics, but SSIM or HDR-VDP catches perceptual differences that raw math misses. That sounds rigorous—until you realize temporal artifacts (flickering shadows, LOD pop-in, dithering patterns) won't show in a lone frame. You need a camera sweep or a gameplay loop recorded at 60 fps. Compare side-by-side with the same player input. Wrong order: adjusting settings first, then taking the 'after' shot. Do the capture before you touch anything. Most teams skip this and end up arguing about whether the specular highlight changed or just the monitor brightness shifted. I have seen a lead insist a pipeline adjustment improved quality—turned out the ambient occlusion was just turned off.
What is the lone most effective optimization?
Kill what you cannot see. Cull aggressively—frustum culling, occlusion culling, small-object culling. It sounds trivial, but I have profiled projects where 40% of submitted meshes were fully occluded behind a lone pillar. The pitfall is culling too early and introducing pop-in. Set your culling distance per object category, not globally. A chair can vanish at 20 meters without notice; a main character's idle animation breaking at 15 meters is a bug. That said, the biggest return I ever squeezed came from reducing shader variants—not from any fancy pipeline. We had 147 variants for a single character material. Compiling them blew the cache, spiked draw-call setup, and wasted VRAM. We cut to 23 variants and gained 3ms per frame. One shift. No visual loss.
We optimized the tileset and saved 1ms. We culled the hidden geometry and saved 4ms. The players noticed only the cull, because the fan behind the wall stopped spinning.
— Lead technical artist, on why visibility wins over shading tricks
Checklist: profile, adjust, compare, repeat
- Capture a baseline before any adjustment—screenshot, log, frame time graph.
- Set a visual-fidelity pass/fail: define one deliberate worst-case camera angle.
- check on your worst target hardware, not your development rig.
- Adjust one variable per iteration—never 'also enabled shadow caching' in the same run.
- Compare the new frame against the reference using per-pixel error, not gut feeling.
- Log the trade-off: each 1ms saved should name what degraded (shadow resolution, LOD distance, culling aggressiveness).
- If visual difference appears, revert and halve the adjustment—degrade 50% less and retest.
Follow that loop for three rounds before you declare victory. The fourth round often reveals a bottleneck you ignored because you stared at the GPU billboard and forgot the CPU thread that schedules it. Stop when you hit your frame budget and a blind A/B test between old and new gets mixed reviews. That is the line between optimization and destruction. Cross it anyway—then cross back.
A community mentor says however confident you feel, rehearse the failure case once before you ship the shift.
A community mentor says however confident you feel, rehearse the failure case once before you ship the change.
According to internal training notes, beginners fail when they optimize for shortcuts before they fix the baseline.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!