Blender V5.0
texture_margin.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_assert.h"
10#include "BLI_math_geom.h"
11#include "BLI_math_vector.hh"
13#include "BLI_vector.hh"
14
15#include "BKE_attribute.hh"
16#include "BKE_customdata.hh"
17#include "BKE_mesh.hh"
18#include "BKE_mesh_mapping.hh"
19
20#include "IMB_imbuf.hh"
21#include "IMB_interp.hh"
22
23#include "MEM_guardedalloc.h"
24
25#include "zbuf.h" /* For rasterizer (#ZSpan and associated functions). */
26
27#include "RE_texture_margin.h"
28
29#include <algorithm>
30#include <cmath>
31
33
39 static const int directions[8][2];
40 static const int distances[8];
41
43 Vector<int> loop_adjacency_map_;
45 Array<int> loop_to_face_map_;
46
47 int w_, h_;
48 float uv_offset_[2];
49 Vector<uint32_t> pixel_data_;
50 ZSpan zspan_;
51 uint32_t value_to_store_;
52 bool write_mask_;
53 char *mask_;
54
55 OffsetIndices<int> faces_;
56 Span<int> corner_edges_;
57 Span<float2> uv_map_;
58 int totedge_;
59
60 public:
62 size_t h,
63 const float uv_offset[2],
64 const int totedge,
66 const Span<int> corner_edges,
67 const Span<float2> uv_map)
68 : w_(w),
69 h_(h),
70 faces_(faces),
71 corner_edges_(corner_edges),
72 uv_map_(uv_map),
73 totedge_(totedge)
74 {
75 copy_v2_v2(uv_offset_, uv_offset);
76
77 pixel_data_.resize(w_ * h_, 0xFFFFFFFF);
78
79 zbuf_alloc_span(&zspan_, w_, h_);
80
81 build_tables();
82 }
83
85 {
86 zbuf_free_span(&zspan_);
87 }
88
89 void set_pixel(int x, int y, uint32_t value)
90 {
91 BLI_assert(x < w_);
92 BLI_assert(x >= 0);
93 pixel_data_[y * w_ + x] = value;
94 }
95
96 uint32_t get_pixel(int x, int y) const
97 {
98 if (x < 0 || y < 0 || x >= w_ || y >= h_) {
99 return 0xFFFFFFFF;
100 }
101
102 return pixel_data_[y * w_ + x];
103 }
104
105 void rasterize_tri(float *v1, float *v2, float *v3, uint32_t value, char *mask, bool writemask)
106 {
107 /* NOTE: This is not thread safe, because the value to be written by the rasterizer is
108 * a class member. If this is ever made multi-threaded each thread needs to get its own. */
109 value_to_store_ = value;
110 mask_ = mask;
111 write_mask_ = writemask;
113 &zspan_, this, &(v1[0]), &(v2[0]), &(v3[0]), TextureMarginMap::zscan_store_pixel);
114 }
115
116 static void zscan_store_pixel(
117 void *map, int x, int y, [[maybe_unused]] float u, [[maybe_unused]] float v)
118 {
119 /* NOTE: Not thread safe, see comment above. */
120 TextureMarginMap *m = static_cast<TextureMarginMap *>(map);
121 if (m->mask_) {
122 if (m->write_mask_) {
123 /* if there is a mask and write_mask_ is true, write to the mask */
124 m->mask_[y * m->w_ + x] = 1;
125 m->set_pixel(x, y, m->value_to_store_);
126 }
127 else {
128 /* if there is a mask and write_mask_ is false, read the mask
129 * to decide if the map needs to be written
130 */
131 if (m->mask_[y * m->w_ + x] != 0) {
132 m->set_pixel(x, y, m->value_to_store_);
133 }
134 }
135 }
136 else {
137 m->set_pixel(x, y, m->value_to_store_);
138 }
139 }
140
141/* The map contains 2 kinds of pixels: DijkstraPixels and face indices. The top bit determines
142 * what kind it is. With the top bit set, it is a 'dijkstra' pixel. The bottom 4 bits encode the
143 * direction of the shortest path and the remaining 27 bits are used to store the distance. If
144 * the top bit is not set, the rest of the bits is used to store the face index.
145 */
146#define PackDijkstraPixel(dist, dir) (0x80000000 + ((dist) << 4) + (dir))
147#define DijkstraPixelGetDistance(dp) (((dp) ^ 0x80000000) >> 4)
148#define DijkstraPixelGetDirection(dp) ((dp) & 0xF)
149#define IsDijkstraPixel(dp) ((dp) & 0x80000000)
150#define DijkstraPixelIsUnset(dp) ((dp) == 0xFFFFFFFF)
151
156 void grow_dijkstra(int margin)
157 {
158 class DijkstraActivePixel {
159 public:
160 DijkstraActivePixel(int dist, int _x, int _y) : distance(dist), x(_x), y(_y) {}
161 int distance;
162 int x, y;
163 };
164 auto cmp_dijkstrapixel_fun = [](DijkstraActivePixel const &a1, DijkstraActivePixel const &a2) {
165 return a1.distance > a2.distance;
166 };
167
168 Vector<DijkstraActivePixel> active_pixels;
169 for (int y = 0; y < h_; y++) {
170 for (int x = 0; x < w_; x++) {
172 for (int i = 0; i < 8; i++) {
173 int xx = x - directions[i][0];
174 int yy = y - directions[i][1];
175
176 if (xx >= 0 && xx < w_ && yy >= 0 && yy < w_ && !IsDijkstraPixel(get_pixel(xx, yy))) {
177 set_pixel(x, y, PackDijkstraPixel(distances[i], i));
178 active_pixels.append(DijkstraActivePixel(distances[i], x, y));
179 break;
180 }
181 }
182 }
183 }
184 }
185
186 /* Not strictly needed because at this point it already is a heap. */
187#if 0
188 std::make_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun);
189#endif
190
191 while (active_pixels.size()) {
192 std::pop_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun);
193 DijkstraActivePixel p = active_pixels.pop_last();
194
195 int dist = p.distance;
196
197 if (dist < 2 * (margin + 1)) {
198 for (int i = 0; i < 8; i++) {
199 int x = p.x + directions[i][0];
200 int y = p.y + directions[i][1];
201 if (x >= 0 && x < w_ && y >= 0 && y < h_) {
202 uint32_t dp = get_pixel(x, y);
203 if (IsDijkstraPixel(dp) && (DijkstraPixelGetDistance(dp) > dist + distances[i])) {
205 set_pixel(x, y, PackDijkstraPixel(dist + distances[i], i));
206 active_pixels.append(DijkstraActivePixel(dist + distances[i], x, y));
207 std::push_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun);
208 }
209 }
210 }
211 }
212 }
213 }
214
220 void lookup_pixels(ImBuf *ibuf, char *mask, int maxPolygonSteps)
221 {
222 float4 *ibuf_ptr_fl = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
223 uchar4 *ibuf_ptr_ch = reinterpret_cast<uchar4 *>(ibuf->byte_buffer.data);
224 size_t pixel_index = 0;
225 for (int y = 0; y < h_; y++) {
226 for (int x = 0; x < w_; x++) {
227 uint32_t dp = pixel_data_[pixel_index];
228 if (IsDijkstraPixel(dp) && !DijkstraPixelIsUnset(dp)) {
229 int dist = DijkstraPixelGetDistance(dp);
230 int direction = DijkstraPixelGetDirection(dp);
231
232 int xx = x;
233 int yy = y;
234
235 /* Follow the dijkstra directions to find the face this margin pixels belongs to. */
236 while (dist > 0) {
237 xx -= directions[direction][0];
238 yy -= directions[direction][1];
239 dp = get_pixel(xx, yy);
240 dist -= distances[direction];
241 BLI_assert(!dist || (dist == DijkstraPixelGetDistance(dp)));
242 direction = DijkstraPixelGetDirection(dp);
243 }
244
245 uint32_t face = get_pixel(xx, yy);
246
248
249 float destX, destY;
250
251 int other_poly;
252 bool found_pixel_in_polygon = false;
253 if (lookup_pixel_polygon_neighborhood(x, y, &face, &destX, &destY, &other_poly)) {
254
255 for (int i = 0; i < maxPolygonSteps; i++) {
256 /* Force to pixel grid. */
257 int nx = int(round(destX));
258 int ny = int(round(destY));
259 uint32_t polygon_from_map = get_pixel(nx, ny);
260 if (other_poly == polygon_from_map) {
261 found_pixel_in_polygon = true;
262 break;
263 }
264
265 float dist_to_edge;
266 /* Look up again, but starting from the face we were expected to land in. */
267 if (!lookup_pixel(nx, ny, other_poly, &destX, &destY, &other_poly, &dist_to_edge)) {
268 found_pixel_in_polygon = false;
269 break;
270 }
271 }
272
273 if (found_pixel_in_polygon) {
274 if (ibuf_ptr_fl) {
275 ibuf_ptr_fl[pixel_index] = imbuf::interpolate_bilinear_border_fl(
276 ibuf, destX, destY);
277 }
278 if (ibuf_ptr_ch) {
279 ibuf_ptr_ch[pixel_index] = imbuf::interpolate_bilinear_border_byte(
280 ibuf, destX, destY);
281 }
282 /* Add our new pixels to the assigned pixel map. */
283 mask[pixel_index] = 1;
284 }
285 }
286 }
287 else if (DijkstraPixelIsUnset(dp) || !IsDijkstraPixel(dp)) {
288 /* These are not margin pixels, make sure the extend filter which is run after this step
289 * leaves them alone.
290 */
291 mask[pixel_index] = 1;
292 }
293 pixel_index++;
294 }
295 }
296 }
297
298 private:
299 float2 uv_to_xy(const float2 &uv_map) const
300 {
301 float2 ret;
302 ret.x = (((uv_map[0] - uv_offset_[0]) * w_) - (0.5f + 0.001f));
303 ret.y = (((uv_map[1] - uv_offset_[1]) * h_) - (0.5f + 0.001f));
304 return ret;
305 }
306
307 void build_tables()
308 {
309 loop_to_face_map_ = blender::bke::mesh::build_corner_to_face_map(faces_);
310
311 loop_adjacency_map_.resize(corner_edges_.size(), -1);
312
313 Vector<int> tmpmap;
314 tmpmap.resize(totedge_, -1);
315
316 for (const int64_t i : corner_edges_.index_range()) {
317 int edge = corner_edges_[i];
318 if (tmpmap[edge] == -1) {
319 loop_adjacency_map_[i] = -1;
320 tmpmap[edge] = i;
321 }
322 else {
323 BLI_assert(tmpmap[edge] >= 0);
324 loop_adjacency_map_[i] = tmpmap[edge];
325 loop_adjacency_map_[tmpmap[edge]] = i;
326 }
327 }
328 }
329
336 bool lookup_pixel_polygon_neighborhood(
337 float x, float y, uint32_t *r_start_poly, float *r_destx, float *r_desty, int *r_other_poly)
338 {
339 float found_dist;
340 if (lookup_pixel(x, y, *r_start_poly, r_destx, r_desty, r_other_poly, &found_dist)) {
341 return true;
342 }
343
344 int loopstart = faces_[*r_start_poly].start();
345 int totloop = faces_[*r_start_poly].size();
346
347 float destx, desty;
348 int foundpoly;
349
350 float mindist = -1.0f;
351
352 /* Loop over all adjacent polygons and determine which edge is closest.
353 * This could be optimized by only inspecting neighbors which are on the edge of an island.
354 * But it seems fast enough for now and that would add a lot of complexity. */
355 for (int i = 0; i < totloop; i++) {
356 int otherloop = loop_adjacency_map_[i + loopstart];
357
358 if (otherloop < 0) {
359 continue;
360 }
361
362 uint32_t face = loop_to_face_map_[otherloop];
363
364 if (lookup_pixel(x, y, face, &destx, &desty, &foundpoly, &found_dist)) {
365 if (mindist < 0.0f || found_dist < mindist) {
366 mindist = found_dist;
367 *r_other_poly = foundpoly;
368 *r_destx = destx;
369 *r_desty = desty;
370 *r_start_poly = face;
371 }
372 }
373 }
374
375 return mindist >= 0.0f;
376 }
377
384 bool lookup_pixel(float x,
385 float y,
386 int src_poly,
387 float *r_destx,
388 float *r_desty,
389 int *r_other_poly,
390 float *r_dist_to_edge)
391 {
392 float2 point(x, y);
393
394 *r_destx = *r_desty = 0;
395
396 int found_edge = -1;
397 float found_dist = -1;
398 float found_t = 0;
399
400 /* Find the closest edge on which the point x,y can be projected.
401 */
402 for (size_t i = 0; i < faces_[src_poly].size(); i++) {
403 int l1 = faces_[src_poly].start() + i;
404 int l2 = l1 + 1;
405 if (l2 >= faces_[src_poly].start() + faces_[src_poly].size()) {
406 l2 = faces_[src_poly].start();
407 }
408 /* edge points */
409 float2 edgepoint1 = uv_to_xy(uv_map_[l1]);
410 float2 edgepoint2 = uv_to_xy(uv_map_[l2]);
411 /* Vector AB is the vector from the first edge point to the second edge point.
412 * Vector AP is the vector from the first edge point to our point under investigation. */
413 float2 ab = edgepoint2 - edgepoint1;
414 float2 ap = point - edgepoint1;
415
416 /* Project ap onto ab. */
417 float dotv = math::dot(ab, ap);
418
419 float ablensq = math::length_squared(ab);
420
421 float t = dotv / ablensq;
422
423 if (t >= 0.0 && t <= 1.0) {
424
425 /* Find the point on the edge closest to P */
426 float2 reflect_point = edgepoint1 + (t * ab);
427 /* This is the vector to P, so 90 degrees out from the edge. */
428 float2 reflect_vec = reflect_point - point;
429
430 float reflectLen = sqrt(reflect_vec[0] * reflect_vec[0] + reflect_vec[1] * reflect_vec[1]);
431 float cross = ab[0] * reflect_vec[1] - ab[1] * reflect_vec[0];
432 /* Only if P is on the outside of the edge, which means the cross product is positive,
433 * we consider this edge.
434 */
435 bool valid = (cross > 0.0);
436
437 if (valid && (found_dist < 0 || reflectLen < found_dist)) {
438 /* Stother_ab the info of the closest edge so far. */
439 found_dist = reflectLen;
440 found_t = t;
441 found_edge = i + faces_[src_poly].start();
442 }
443 }
444 }
445
446 if (found_edge < 0) {
447 return false;
448 }
449
450 *r_dist_to_edge = found_dist;
451
452 /* Get the 'other' edge. I.E. the UV edge from the neighbor face. */
453 int other_edge = loop_adjacency_map_[found_edge];
454
455 if (other_edge < 0) {
456 return false;
457 }
458
459 int dst_poly = loop_to_face_map_[other_edge];
460
461 if (r_other_poly) {
462 *r_other_poly = dst_poly;
463 }
464
465 int other_edge2 = other_edge + 1;
466 if (other_edge2 >= faces_[dst_poly].start() + faces_[dst_poly].size()) {
467 other_edge2 = faces_[dst_poly].start();
468 }
469
470 float2 other_edgepoint1 = uv_to_xy(uv_map_[other_edge]);
471 float2 other_edgepoint2 = uv_to_xy(uv_map_[other_edge2]);
472
473 /* Calculate the vector from the order edges last point to its first point. */
474 float2 other_ab = other_edgepoint1 - other_edgepoint2;
475 float2 other_reflect_point = other_edgepoint2 + (found_t * other_ab);
476 float2 perpendicular_other_ab;
477 perpendicular_other_ab.x = other_ab.y;
478 perpendicular_other_ab.y = -other_ab.x;
479
480 /* The new point is dound_dist distance from other_reflect_point at a 90 degree angle to
481 * other_ab */
482 float2 new_point = other_reflect_point + (found_dist / math::length(perpendicular_other_ab)) *
483 perpendicular_other_ab;
484
485 *r_destx = new_point.x;
486 *r_desty = new_point.y;
487
488 return true;
489 }
490}; // class TextureMarginMap
491
492const int TextureMarginMap::directions[8][2] = {
493 {-1, 0}, {-1, -1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}};
494const int TextureMarginMap::distances[8] = {2, 3, 2, 3, 2, 3, 2, 3};
495
496static void generate_margin(ImBuf *ibuf,
497 char *mask,
498 const int margin,
499 const Span<float3> vert_positions,
500 const int edges_num,
502 const Span<int> corner_edges,
503 const Span<int> corner_verts,
504 const Span<float2> uv_map,
505 const float uv_offset[2])
506{
507 Array<int3> corner_tris(poly_to_tri_count(faces.size(), corner_edges.size()));
508 bke::mesh::corner_tris_calc(vert_positions, faces, corner_verts, corner_tris);
509
510 Array<int> tri_faces(corner_tris.size());
512
513 TextureMarginMap map(ibuf->x, ibuf->y, uv_offset, edges_num, faces, corner_edges, uv_map);
514
515 bool draw_new_mask = false;
516 /* Now the map contains 3 sorts of values: 0xFFFFFFFF for empty pixels, `0x80000000 + polyindex`
517 * for margin pixels, just `polyindex` for face pixels. */
518 if (mask) {
519 mask = (char *)MEM_dupallocN(mask);
520 }
521 else {
522 mask = MEM_calloc_arrayN<char>(size_t(ibuf->x) * size_t(ibuf->y), __func__);
523 draw_new_mask = true;
524 }
525
526 for (const int i : corner_tris.index_range()) {
527 const int3 tri = corner_tris[i];
528 float vec[3][2];
529
530 for (int a = 0; a < 3; a++) {
531 const float *uv = uv_map[tri[a]];
532
533 /* NOTE(@ideasman42): workaround for pixel aligned UVs which are common and can screw up
534 * our intersection tests where a pixel gets in between 2 faces or the middle of a quad,
535 * camera aligned quads also have this problem but they are less common.
536 * Add a small offset to the UVs, fixes bug #18685. */
537 vec[a][0] = (uv[0] - uv_offset[0]) * float(ibuf->x) - (0.5f + 0.001f);
538 vec[a][1] = (uv[1] - uv_offset[1]) * float(ibuf->y) - (0.5f + 0.002f);
539 }
540
541 /* NOTE: we need the top bit for the dijkstra distance map. */
542 BLI_assert(tri_faces[i] < 0x80000000);
543
544 map.rasterize_tri(vec[0], vec[1], vec[2], tri_faces[i], mask, draw_new_mask);
545 }
546
547 char *tmpmask = (char *)MEM_dupallocN(mask);
548 /* Extend (with averaging) by 2 pixels. Those will be overwritten, but it
549 * helps linear interpolations on the edges of polygons. */
550 IMB_filter_extend(ibuf, tmpmask, 2);
551 MEM_freeN(tmpmask);
552
553 map.grow_dijkstra(margin);
554
555 /* Looking further than 3 polygons away leads to so much cumulative rounding
556 * that it isn't worth it. So hard-code it to 3. */
557 map.lookup_pixels(ibuf, mask, 3);
558
559 /* Use the extend filter to fill in the missing pixels at the corners, not strictly correct, but
560 * the visual difference seems very minimal. This also catches pixels we missed because of very
561 * narrow polygons.
562 */
563 IMB_filter_extend(ibuf, mask, margin);
564
566}
567
568} // namespace blender::render::texturemargin
569
571 char *mask,
572 const int margin,
573 const Mesh *mesh,
574 blender::StringRef uv_layer,
575 const float uv_offset[2])
576{
577 using namespace blender;
578 const StringRef name = uv_layer.is_empty() ?
580 uv_layer;
581 const blender::bke::AttributeAccessor attributes = mesh->attributes();
582 const VArraySpan<float2> uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Corner);
583
585 mask,
586 margin,
587 mesh->vert_positions(),
588 mesh->edges_num,
589 mesh->faces(),
590 mesh->corner_edges(),
591 mesh->corner_verts(),
592 uv_map,
593 uv_offset);
594}
CustomData interface, see also DNA_customdata_types.h.
const char * CustomData_get_active_layer_name(const CustomData *data, eCustomDataType type)
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE int poly_to_tri_count(int poly_count, int corner_count)
MINLINE void copy_v2_v2(float r[2], const float a[2])
@ CD_PROP_FLOAT2
void IMB_filter_extend(ImBuf *ibuf, char *mask, int filter)
Definition filter.cc:200
Read Guarded memory(de)allocation.
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert * v
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
AttributeSet attributes
int64_t size() const
Definition BLI_array.hh:256
IndexRange index_range() const
Definition BLI_array.hh:360
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
int64_t size() const
void append(const T &value)
void resize(const int64_t new_size)
GAttributeReader lookup(const StringRef attribute_id) const
void rasterize_tri(float *v1, float *v2, float *v3, uint32_t value, char *mask, bool writemask)
void set_pixel(int x, int y, uint32_t value)
TextureMarginMap(size_t w, size_t h, const float uv_offset[2], const int totedge, const OffsetIndices< int > faces, const Span< int > corner_edges, const Span< float2 > uv_map)
void lookup_pixels(ImBuf *ibuf, char *mask, int maxPolygonSteps)
static void zscan_store_pixel(void *map, int x, int y, float u, float v)
#define round
#define sqrt
VecBase< float, 3 > cross(VecOp< float, 3 >, VecOp< float, 3 >) RET
float distance(VecOp< float, D >, VecOp< float, D >) RET
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static char faces[256]
void corner_tris_calc(Span< float3 > vert_positions, OffsetIndices< int > faces, Span< int > corner_verts, MutableSpan< int3 > corner_tris)
Array< int > build_corner_to_face_map(OffsetIndices< int > faces)
void corner_tris_calc_face_indices(OffsetIndices< int > faces, MutableSpan< int > tri_faces)
uchar4 interpolate_bilinear_border_byte(const ImBuf *in, float u, float v)
Definition IMB_interp.hh:74
float4 interpolate_bilinear_border_fl(const ImBuf *in, float u, float v)
Definition IMB_interp.hh:78
T length_squared(const VecBase< T, Size > &a)
T length(const VecBase< T, Size > &a)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
static void generate_margin(ImBuf *ibuf, char *mask, const int margin, const Span< float3 > vert_positions, const int edges_num, const OffsetIndices< int > faces, const Span< int > corner_edges, const Span< int > corner_verts, const Span< float2 > uv_map, const float uv_offset[2])
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 4 > float4
VecBase< float, 2 > float2
VecBase< int32_t, 3 > int3
const char * name
return ret
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
int edges_num
CustomData corner_data
Definition zbuf.h:12
float x
float y
i
Definition text_draw.cc:230
#define PackDijkstraPixel(dist, dir)
#define DijkstraPixelIsUnset(dp)
#define DijkstraPixelGetDistance(dp)
#define DijkstraPixelGetDirection(dp)
#define IsDijkstraPixel(dp)
void RE_generate_texturemargin_adjacentfaces(ImBuf *ibuf, char *mask, const int margin, const Mesh *mesh, blender::StringRef uv_layer, const float uv_offset[2])
void zspan_scanconvert(ZSpan *zspan, void *handle, float *v1, float *v2, float *v3, void(*func)(void *, int, int, float, float))
Definition zbuf.cc:151
void zbuf_alloc_span(ZSpan *zspan, int rectx, int recty)
Definition zbuf.cc:33
void zbuf_free_span(ZSpan *zspan)
Definition zbuf.cc:44