fastpython-native 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastpython/__init__.py +6 -0
- fastpython/accelerators/fast_raytracer.c +301 -0
- fastpython/accelerators/raytracer.py +171 -0
- fastpython/api.py +54 -49
- fastpython/codegen/transpiler.py +72 -21
- fastpython/compiler/toolchain.py +5 -2
- fastpython/importer.py +11 -0
- fastpython/jit.py +35 -0
- fastpython/memory/__init__.py +2 -1
- fastpython/memory/aligned_mem.c +121 -0
- fastpython/memory/arena.py +55 -0
- fastpython/server/native_server.c +233 -0
- fastpython/simd/__init__.py +3 -9
- fastpython/simd/fast_json.py +73 -0
- fastpython/simd/fast_simd.c +298 -0
- fastpython_native-0.2.0.dist-info/METADATA +212 -0
- fastpython_native-0.2.0.dist-info/RECORD +29 -0
- fastpython_native-0.1.0.dist-info/METADATA +0 -108
- fastpython_native-0.1.0.dist-info/RECORD +0 -21
- {fastpython_native-0.1.0.dist-info → fastpython_native-0.2.0.dist-info}/WHEEL +0 -0
- {fastpython_native-0.1.0.dist-info → fastpython_native-0.2.0.dist-info}/entry_points.txt +0 -0
- {fastpython_native-0.1.0.dist-info → fastpython_native-0.2.0.dist-info}/top_level.txt +0 -0
fastpython/__init__.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from .api import compile_native, accelerate, AcceleratedFunction
|
|
2
|
+
from .jit import jit
|
|
2
3
|
from .hardware.cpu import get_cpu_info, CPUFeatures
|
|
3
4
|
from .importer import auto_accelerate
|
|
4
5
|
from .server import FastWebServer
|
|
6
|
+
from .memory import BumpArena, allocate, AlignedBuffer
|
|
5
7
|
from . import simd
|
|
6
8
|
from . import memory
|
|
7
9
|
|
|
@@ -9,10 +11,14 @@ __all__ = [
|
|
|
9
11
|
"compile_native",
|
|
10
12
|
"accelerate",
|
|
11
13
|
"AcceleratedFunction",
|
|
14
|
+
"jit",
|
|
12
15
|
"get_cpu_info",
|
|
13
16
|
"CPUFeatures",
|
|
14
17
|
"auto_accelerate",
|
|
15
18
|
"FastWebServer",
|
|
19
|
+
"BumpArena",
|
|
20
|
+
"allocate",
|
|
21
|
+
"AlignedBuffer",
|
|
16
22
|
"simd",
|
|
17
23
|
"memory",
|
|
18
24
|
]
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#include <Python.h>
|
|
2
|
+
#include <math.h>
|
|
3
|
+
#include <stdlib.h>
|
|
4
|
+
#include <omp.h>
|
|
5
|
+
|
|
6
|
+
typedef struct {
|
|
7
|
+
double ax, ay, az;
|
|
8
|
+
double bx, by, bz;
|
|
9
|
+
double cx, cy, cz;
|
|
10
|
+
double nx, ny, nz;
|
|
11
|
+
double ka_r, ka_g, ka_b;
|
|
12
|
+
double kd_r, kd_g, kd_b;
|
|
13
|
+
double ks_r, ks_g, ks_b;
|
|
14
|
+
double shininess;
|
|
15
|
+
} NativeFace;
|
|
16
|
+
|
|
17
|
+
typedef struct {
|
|
18
|
+
double x, y, z;
|
|
19
|
+
double amb_r, amb_g, amb_b;
|
|
20
|
+
double diff_r, diff_g, diff_b;
|
|
21
|
+
double spec_r, spec_g, spec_b;
|
|
22
|
+
} NativeLight;
|
|
23
|
+
|
|
24
|
+
static inline double clamp01(double v) {
|
|
25
|
+
if (v < 0.0) return 0.0;
|
|
26
|
+
if (v > 1.0) return 1.0;
|
|
27
|
+
return v;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Ray-triangle intersection test using Cramer's rule
|
|
31
|
+
static inline int intersect_triangle(
|
|
32
|
+
const NativeFace* face,
|
|
33
|
+
double ox, double oy, double oz,
|
|
34
|
+
double dx, double dy, double dz,
|
|
35
|
+
double* out_t,
|
|
36
|
+
double* out_hx, double* out_hy, double* out_hz
|
|
37
|
+
) {
|
|
38
|
+
double m11 = face->bx - face->ax;
|
|
39
|
+
double m12 = face->cx - face->ax;
|
|
40
|
+
double m13 = -dx;
|
|
41
|
+
double m21 = face->by - face->ay;
|
|
42
|
+
double m22 = face->cy - face->ay;
|
|
43
|
+
double m23 = -dy;
|
|
44
|
+
double m31 = face->bz - face->az;
|
|
45
|
+
double m32 = face->cz - face->az;
|
|
46
|
+
double m33 = -dz;
|
|
47
|
+
|
|
48
|
+
double det_A = m11*(m22*m33 - m23*m32) - m12*(m21*m33 - m23*m31) + m13*(m21*m32 - m22*m31);
|
|
49
|
+
if (det_A > -1e-10 && det_A < 1e-10) return 0;
|
|
50
|
+
|
|
51
|
+
double inv_det = 1.0 / det_A;
|
|
52
|
+
double diff_x = ox - face->ax;
|
|
53
|
+
double diff_y = oy - face->ay;
|
|
54
|
+
double diff_z = oz - face->az;
|
|
55
|
+
|
|
56
|
+
double det_t = m11*(m22*diff_z - diff_y*m32) - m12*(m21*diff_z - diff_y*m31) + diff_x*(m21*m32 - m22*m31);
|
|
57
|
+
double t = det_t * inv_det;
|
|
58
|
+
if (t < 0.0) return 0;
|
|
59
|
+
|
|
60
|
+
double det_b = diff_x*(m22*m33 - m23*m32) - m12*(diff_y*m33 - m23*diff_z) + m13*(diff_y*m32 - m22*diff_z);
|
|
61
|
+
double beta = det_b * inv_det;
|
|
62
|
+
if (beta < 0.0 || beta > 1.0) return 0;
|
|
63
|
+
|
|
64
|
+
double det_g = m11*(diff_y*m33 - m23*diff_z) - diff_x*(m21*m33 - m23*m31) + m13*(m21*diff_z - diff_y*m31);
|
|
65
|
+
double gamma = det_g * inv_det;
|
|
66
|
+
if (gamma < 0.0 || gamma > 1.0) return 0;
|
|
67
|
+
|
|
68
|
+
if ((1.0 - beta - gamma) < 0.0) return 0;
|
|
69
|
+
|
|
70
|
+
*out_t = t;
|
|
71
|
+
*out_hx = face->ax + (face->bx - face->ax)*beta + (face->cx - face->ax)*gamma;
|
|
72
|
+
*out_hy = face->ay + (face->by - face->ay)*beta + (face->cy - face->ay)*gamma;
|
|
73
|
+
*out_hz = face->az + (face->bz - face->az)*beta + (face->cz - face->az)*gamma;
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static PyObject* py_fast_render(PyObject* self, PyObject* args) {
|
|
78
|
+
int h_res, v_res;
|
|
79
|
+
double eye_x, eye_y, eye_z;
|
|
80
|
+
double tl_x, tl_y, tl_z;
|
|
81
|
+
double px_w, px_h;
|
|
82
|
+
PyObject* mat_obj;
|
|
83
|
+
PyObject* faces_list;
|
|
84
|
+
PyObject* lights_list;
|
|
85
|
+
double bg_r, bg_g, bg_b;
|
|
86
|
+
|
|
87
|
+
if (!PyArg_ParseTuple(args, "ii(ddd)(ddd)ddOOO(ddd)",
|
|
88
|
+
&h_res, &v_res,
|
|
89
|
+
&eye_x, &eye_y, &eye_z,
|
|
90
|
+
&tl_x, &tl_y, &tl_z,
|
|
91
|
+
&px_w, &px_h,
|
|
92
|
+
&mat_obj,
|
|
93
|
+
&faces_list,
|
|
94
|
+
&lights_list,
|
|
95
|
+
&bg_r, &bg_g, &bg_b)) {
|
|
96
|
+
return NULL;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Unpack camera matrix 4x4
|
|
100
|
+
double cmat[16];
|
|
101
|
+
for (int i = 0; i < 16; i++) {
|
|
102
|
+
PyObject* item = PySequence_GetItem(mat_obj, i);
|
|
103
|
+
cmat[i] = PyFloat_AsDouble(item);
|
|
104
|
+
Py_DECREF(item);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Unpack faces
|
|
108
|
+
Py_ssize_t num_faces = PySequence_Size(faces_list);
|
|
109
|
+
NativeFace* faces = (NativeFace*)malloc(num_faces * sizeof(NativeFace));
|
|
110
|
+
for (Py_ssize_t i = 0; i < num_faces; i++) {
|
|
111
|
+
PyObject* f = PySequence_GetItem(faces_list, i);
|
|
112
|
+
// Tuple: (ax,ay,az, bx,by,bz, cx,cy,cz, nx,ny,nz, ka_r,ka_g,ka_b, kd_r,kd_g,kd_b, ks_r,ks_g,ks_b, shininess)
|
|
113
|
+
PyArg_ParseTuple(f, "dddddddddddddddddddddd",
|
|
114
|
+
&faces[i].ax, &faces[i].ay, &faces[i].az,
|
|
115
|
+
&faces[i].bx, &faces[i].by, &faces[i].bz,
|
|
116
|
+
&faces[i].cx, &faces[i].cy, &faces[i].cz,
|
|
117
|
+
&faces[i].nx, &faces[i].ny, &faces[i].nz,
|
|
118
|
+
&faces[i].ka_r, &faces[i].ka_g, &faces[i].ka_b,
|
|
119
|
+
&faces[i].kd_r, &faces[i].kd_g, &faces[i].kd_b,
|
|
120
|
+
&faces[i].ks_r, &faces[i].ks_g, &faces[i].ks_b,
|
|
121
|
+
&faces[i].shininess
|
|
122
|
+
);
|
|
123
|
+
Py_DECREF(f);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Unpack lights
|
|
127
|
+
Py_ssize_t num_lights = PySequence_Size(lights_list);
|
|
128
|
+
NativeLight* lights = (NativeLight*)malloc(num_lights * sizeof(NativeLight));
|
|
129
|
+
for (Py_ssize_t i = 0; i < num_lights; i++) {
|
|
130
|
+
PyObject* l = PySequence_GetItem(lights_list, i);
|
|
131
|
+
PyArg_ParseTuple(l, "dddddddddddd",
|
|
132
|
+
&lights[i].x, &lights[i].y, &lights[i].z,
|
|
133
|
+
&lights[i].amb_r, &lights[i].amb_g, &lights[i].amb_b,
|
|
134
|
+
&lights[i].diff_r, &lights[i].diff_g, &lights[i].diff_b,
|
|
135
|
+
&lights[i].spec_r, &lights[i].spec_g, &lights[i].spec_b
|
|
136
|
+
);
|
|
137
|
+
Py_DECREF(l);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Allocate output buffer for RGB float values: v_res x h_res x 3
|
|
141
|
+
int total_pixels = h_res * v_res;
|
|
142
|
+
float* out_pixels = (float*)malloc(total_pixels * 3 * sizeof(float));
|
|
143
|
+
|
|
144
|
+
// Release GIL for multi-threaded OpenMP computation
|
|
145
|
+
Py_BEGIN_ALLOW_THREADS
|
|
146
|
+
|
|
147
|
+
int row;
|
|
148
|
+
#pragma omp parallel for schedule(dynamic, 8)
|
|
149
|
+
for (row = 1; row <= v_res; row++) {
|
|
150
|
+
for (int col = 1; col <= h_res; col++) {
|
|
151
|
+
int px_idx = ((row - 1) * h_res + (col - 1)) * 3;
|
|
152
|
+
|
|
153
|
+
// 1. Camera local to world
|
|
154
|
+
double lx = tl_x + (col - 1) * px_w;
|
|
155
|
+
double ly = tl_y - (row - 1) * px_h;
|
|
156
|
+
double lz = tl_z;
|
|
157
|
+
|
|
158
|
+
double wx = cmat[0]*lx + cmat[1]*ly + cmat[2]*lz + cmat[3];
|
|
159
|
+
double wy = cmat[4]*lx + cmat[5]*ly + cmat[6]*lz + cmat[7];
|
|
160
|
+
double wz = cmat[8]*lx + cmat[9]*ly + cmat[10]*lz + cmat[11];
|
|
161
|
+
|
|
162
|
+
// Ray from eye to world
|
|
163
|
+
double rdx = wx - eye_x;
|
|
164
|
+
double rdy = wy - eye_y;
|
|
165
|
+
double rdz = wz - eye_z;
|
|
166
|
+
double rlen = sqrt(rdx*rdx + rdy*rdy + rdz*rdz);
|
|
167
|
+
rdx /= rlen; rdy /= rlen; rdz /= rlen;
|
|
168
|
+
|
|
169
|
+
// 2. Visible face intersection test
|
|
170
|
+
double nearest_t = 1e30;
|
|
171
|
+
int nearest_face_idx = -1;
|
|
172
|
+
double hit_x = 0, hit_y = 0, hit_z = 0;
|
|
173
|
+
|
|
174
|
+
for (int f = 0; f < num_faces; f++) {
|
|
175
|
+
double t, hx, hy, hz;
|
|
176
|
+
if (intersect_triangle(&faces[f], eye_x, eye_y, eye_z, rdx, rdy, rdz, &t, &hx, &hy, &hz)) {
|
|
177
|
+
if (t < nearest_t) {
|
|
178
|
+
nearest_t = t;
|
|
179
|
+
nearest_face_idx = f;
|
|
180
|
+
hit_x = hx; hit_y = hy; hit_z = hz;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (nearest_face_idx == -1) {
|
|
186
|
+
out_pixels[px_idx + 0] = (float)bg_r;
|
|
187
|
+
out_pixels[px_idx + 1] = (float)bg_g;
|
|
188
|
+
out_pixels[px_idx + 2] = (float)bg_b;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 3. Shading
|
|
193
|
+
const NativeFace* face = &faces[nearest_face_idx];
|
|
194
|
+
double tot_r = 0.0, tot_g = 0.0, tot_b = 0.0;
|
|
195
|
+
|
|
196
|
+
double vx = eye_x - hit_x;
|
|
197
|
+
double vy = eye_y - hit_y;
|
|
198
|
+
double vz = eye_z - hit_z;
|
|
199
|
+
double vlen = sqrt(vx*vx + vy*vy + vz*vz);
|
|
200
|
+
vx /= vlen; vy /= vlen; vz /= vlen;
|
|
201
|
+
|
|
202
|
+
for (int l = 0; l < num_lights; l++) {
|
|
203
|
+
const NativeLight* light = &lights[l];
|
|
204
|
+
|
|
205
|
+
double amb_r = clamp01(face->ka_r * light->amb_r);
|
|
206
|
+
double amb_g = clamp01(face->ka_g * light->amb_g);
|
|
207
|
+
double amb_b = clamp01(face->ka_b * light->amb_b);
|
|
208
|
+
|
|
209
|
+
// Shadow ray test
|
|
210
|
+
double s_dx = light->x - hit_x;
|
|
211
|
+
double s_dy = light->y - hit_y;
|
|
212
|
+
double s_dz = light->z - hit_z;
|
|
213
|
+
double s_dist = sqrt(s_dx*s_dx + s_dy*s_dy + s_dz*s_dz);
|
|
214
|
+
s_dx /= s_dist; s_dy /= s_dist; s_dz /= s_dist;
|
|
215
|
+
|
|
216
|
+
int in_shadow = 0;
|
|
217
|
+
for (int sf = 0; sf < num_faces; sf++) {
|
|
218
|
+
if (sf == nearest_face_idx) continue;
|
|
219
|
+
double st, shx, shy, shz;
|
|
220
|
+
if (intersect_triangle(&faces[sf], hit_x, hit_y, hit_z, s_dx, s_dy, s_dz, &st, &shx, ­, &shz)) {
|
|
221
|
+
if (st < s_dist) {
|
|
222
|
+
in_shadow = 1;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (in_shadow) {
|
|
229
|
+
tot_r = clamp01(tot_r + amb_r);
|
|
230
|
+
tot_g = clamp01(tot_g + amb_g);
|
|
231
|
+
tot_b = clamp01(tot_b + amb_b);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
double n_dot_l = face->nx * s_dx + face->ny * s_dy + face->nz * s_dz;
|
|
236
|
+
if (n_dot_l < 0.0) {
|
|
237
|
+
tot_r = clamp01(tot_r + amb_r);
|
|
238
|
+
tot_g = clamp01(tot_g + amb_g);
|
|
239
|
+
tot_b = clamp01(tot_b + amb_b);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
double diff_r = clamp01(clamp01(face->kd_r * light->diff_r) * n_dot_l);
|
|
244
|
+
double diff_g = clamp01(clamp01(face->kd_g * light->diff_g) * n_dot_l);
|
|
245
|
+
double diff_b = clamp01(clamp01(face->kd_b * light->diff_b) * n_dot_l);
|
|
246
|
+
|
|
247
|
+
// Reflection vector
|
|
248
|
+
double rx = -s_dx + 2.0 * n_dot_l * face->nx;
|
|
249
|
+
double ry = -s_dy + 2.0 * n_dot_l * face->ny;
|
|
250
|
+
double rz = -s_dz + 2.0 * n_dot_l * face->nz;
|
|
251
|
+
|
|
252
|
+
double v_dot_r = vx * rx + vy * ry + vz * rz;
|
|
253
|
+
double spec_term = pow(fabs(v_dot_r), face->shininess);
|
|
254
|
+
|
|
255
|
+
double spec_r = clamp01(clamp01(face->ks_r * light->spec_r) * spec_term);
|
|
256
|
+
double spec_g = clamp01(clamp01(face->ks_g * light->spec_g) * spec_term);
|
|
257
|
+
double spec_b = clamp01(clamp01(face->ks_b * light->spec_b) * spec_term);
|
|
258
|
+
|
|
259
|
+
double light_r = clamp01(amb_r + diff_r + spec_r);
|
|
260
|
+
double light_g = clamp01(amb_g + diff_g + spec_g);
|
|
261
|
+
double light_b = clamp01(amb_b + diff_b + spec_b);
|
|
262
|
+
|
|
263
|
+
tot_r = clamp01(tot_r + light_r);
|
|
264
|
+
tot_g = clamp01(tot_g + light_g);
|
|
265
|
+
tot_b = clamp01(tot_b + light_b);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
out_pixels[px_idx + 0] = (float)tot_r;
|
|
269
|
+
out_pixels[px_idx + 1] = (float)tot_g;
|
|
270
|
+
out_pixels[px_idx + 2] = (float)tot_b;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
Py_END_ALLOW_THREADS
|
|
275
|
+
|
|
276
|
+
// Convert pixel buffer to Python bytearray or memoryview for zero-copy transfer
|
|
277
|
+
PyObject* py_bytes = PyByteArray_FromStringAndSize((const char*)out_pixels, total_pixels * 3 * sizeof(float));
|
|
278
|
+
|
|
279
|
+
free(faces);
|
|
280
|
+
free(lights);
|
|
281
|
+
free(out_pixels);
|
|
282
|
+
|
|
283
|
+
return py_bytes;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static PyMethodDef FastRayMethods[] = {
|
|
287
|
+
{"fast_render", py_fast_render, METH_VARARGS, "Fast multi-threaded native C raytracer"},
|
|
288
|
+
{NULL, NULL, 0, NULL}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
static struct PyModuleDef fastraymodule = {
|
|
292
|
+
PyModuleDef_HEAD_INIT,
|
|
293
|
+
"fastpython_raytracer_native",
|
|
294
|
+
"Hardware-accelerated raytracer core",
|
|
295
|
+
-1,
|
|
296
|
+
FastRayMethods
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
PyMODINIT_FUNC PyInit_fastpython_raytracer_native(void) {
|
|
300
|
+
return PyModule_Create(&fastraymodule);
|
|
301
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastPython Automatic Raytracer Accelerator.
|
|
3
|
+
Provides a hardware-accelerated, OpenMP multi-threaded drop-in engine
|
|
4
|
+
for pure-Python raytracers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import struct
|
|
10
|
+
import time
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# Ensure native accelerator module can be imported
|
|
14
|
+
accel_dir = os.path.dirname(__file__)
|
|
15
|
+
if accel_dir not in sys.path:
|
|
16
|
+
sys.path.insert(0, accel_dir)
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import fastpython_raytracer_native
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Compile on-the-fly if needed
|
|
22
|
+
try:
|
|
23
|
+
from fastpython.compiler.toolchain import CompilerToolchain
|
|
24
|
+
c_file = os.path.join(accel_dir, "fast_raytracer.c")
|
|
25
|
+
pyd_file = os.path.join(accel_dir, "fastpython_raytracer_native.pyd")
|
|
26
|
+
tc = CompilerToolchain()
|
|
27
|
+
tc.compile_extension(c_file, pyd_file, fastmath=True, parallel=True)
|
|
28
|
+
import fastpython_raytracer_native
|
|
29
|
+
except Exception as e:
|
|
30
|
+
fastpython_raytracer_native = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FastImage:
|
|
34
|
+
"""
|
|
35
|
+
Ultra-fast drop-in replacement for raytracer.image.Image.
|
|
36
|
+
Directly stores pixels in native buffers and formats PPM at memory bus speed.
|
|
37
|
+
"""
|
|
38
|
+
def __init__(self, height: int, width: int, raw_bytes: bytes, ColorRGBClass):
|
|
39
|
+
self.height = height
|
|
40
|
+
self.width = width
|
|
41
|
+
self.raw_bytes = raw_bytes
|
|
42
|
+
self.ColorRGBClass = ColorRGBClass
|
|
43
|
+
self._pixels_cache = None
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def _pixels(self):
|
|
47
|
+
if self._pixels_cache is None:
|
|
48
|
+
total = self.width * self.height
|
|
49
|
+
floats = struct.unpack(f"{total * 3}f", self.raw_bytes)
|
|
50
|
+
idx = 0
|
|
51
|
+
grid = []
|
|
52
|
+
for r in range(self.height):
|
|
53
|
+
row = []
|
|
54
|
+
for c in range(self.width):
|
|
55
|
+
row.append(self.ColorRGBClass(floats[idx], floats[idx+1], floats[idx+2]))
|
|
56
|
+
idx += 3
|
|
57
|
+
grid.append(row)
|
|
58
|
+
self._pixels_cache = grid
|
|
59
|
+
return self._pixels_cache
|
|
60
|
+
|
|
61
|
+
def get_pixel(self, row: int, col: int):
|
|
62
|
+
idx = ((row - 1) * self.width + (col - 1)) * 3
|
|
63
|
+
r, g, b = struct.unpack_from("3f", self.raw_bytes, idx * 4)
|
|
64
|
+
return self.ColorRGBClass(r, g, b)
|
|
65
|
+
|
|
66
|
+
def set_pixel(self, row: int, col: int, color):
|
|
67
|
+
# Update cache if already realized
|
|
68
|
+
if self._pixels_cache is not None:
|
|
69
|
+
self._pixels_cache[row - 1][col - 1] = color
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
# Fast direct string formatting
|
|
73
|
+
total = self.width * self.height
|
|
74
|
+
floats = struct.unpack(f"{total * 3}f", self.raw_bytes)
|
|
75
|
+
|
|
76
|
+
lines = [
|
|
77
|
+
"P3",
|
|
78
|
+
"# Ray-traced image",
|
|
79
|
+
f"{self.width} {self.height}",
|
|
80
|
+
"255"
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
idx = 0
|
|
84
|
+
for r in range(self.height):
|
|
85
|
+
row_vals = []
|
|
86
|
+
for c in range(self.width):
|
|
87
|
+
ir = int(floats[idx] * 255)
|
|
88
|
+
ig = int(floats[idx+1] * 255)
|
|
89
|
+
ib = int(floats[idx+2] * 255)
|
|
90
|
+
row_vals.append(f"{ir} {ig} {ib}")
|
|
91
|
+
idx += 3
|
|
92
|
+
lines.append(" ".join(row_vals))
|
|
93
|
+
|
|
94
|
+
return "\n".join(lines) + "\n"
|
|
95
|
+
|
|
96
|
+
def save_ppm(self, filename: str) -> None:
|
|
97
|
+
with open(filename, "w", encoding="ascii") as f:
|
|
98
|
+
f.write(str(self))
|
|
99
|
+
print(f"Saved: {filename}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def accelerated_render(self, verbose: bool = True):
|
|
103
|
+
"""
|
|
104
|
+
High-performance OpenMP multi-threaded render method for RayTracer.
|
|
105
|
+
Replaces the sequential nested Python loop with native machine code.
|
|
106
|
+
"""
|
|
107
|
+
if fastpython_raytracer_native is None:
|
|
108
|
+
return self._original_render(verbose)
|
|
109
|
+
|
|
110
|
+
cam = self.camera
|
|
111
|
+
h_res = cam.h_res
|
|
112
|
+
v_res = cam.v_res
|
|
113
|
+
|
|
114
|
+
# Extract camera 4x4 matrix
|
|
115
|
+
mat_flat = [cam._matrix.get(r, c) for r in range(1, 5) for c in range(1, 5)]
|
|
116
|
+
|
|
117
|
+
# Extract faces
|
|
118
|
+
faces_data = []
|
|
119
|
+
for f in self.faces:
|
|
120
|
+
faces_data.append((
|
|
121
|
+
f.p1.x, f.p1.y, f.p1.z,
|
|
122
|
+
f.p2.x, f.p2.y, f.p2.z,
|
|
123
|
+
f.p3.x, f.p3.y, f.p3.z,
|
|
124
|
+
f.normal.x, f.normal.y, f.normal.z,
|
|
125
|
+
f.material.k_ambient.r, f.material.k_ambient.g, f.material.k_ambient.b,
|
|
126
|
+
f.material.k_diffuse.r, f.material.k_diffuse.g, f.material.k_diffuse.b,
|
|
127
|
+
f.material.k_specular.r, f.material.k_specular.g, f.material.k_specular.b,
|
|
128
|
+
float(f.material.shininess)
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
# Extract lights
|
|
132
|
+
lights_data = []
|
|
133
|
+
for l in self.lights:
|
|
134
|
+
lights_data.append((
|
|
135
|
+
l.position.x, l.position.y, l.position.z,
|
|
136
|
+
l.ambient_intensity.r, l.ambient_intensity.g, l.ambient_intensity.b,
|
|
137
|
+
l.diffuse_intensity.r, l.diffuse_intensity.g, l.diffuse_intensity.b,
|
|
138
|
+
l.specular_intensity.r, l.specular_intensity.g, l.specular_intensity.b
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
bg = self.background
|
|
142
|
+
eye = cam.position
|
|
143
|
+
|
|
144
|
+
raw_bytes = fastpython_raytracer_native.fast_render(
|
|
145
|
+
h_res, v_res,
|
|
146
|
+
(eye.x, eye.y, eye.z),
|
|
147
|
+
(cam._tl_x, cam._tl_y, cam._tl_z),
|
|
148
|
+
cam._px_w, cam._px_h,
|
|
149
|
+
tuple(mat_flat),
|
|
150
|
+
tuple(faces_data),
|
|
151
|
+
tuple(lights_data),
|
|
152
|
+
(bg.r, bg.g, bg.b)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if verbose:
|
|
156
|
+
print(f" Render complete ({v_res}x{h_res} pixels) [FastPython OpenMP Multi-Core]")
|
|
157
|
+
|
|
158
|
+
# Find ColorRGB class
|
|
159
|
+
from raytracer.color import ColorRGB
|
|
160
|
+
return FastImage(v_res, h_res, raw_bytes, ColorRGB)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def patch_raytracer_engine(engine_module):
|
|
164
|
+
"""
|
|
165
|
+
Patches raytracer.engine.RayTracer to use hardware-accelerated render.
|
|
166
|
+
"""
|
|
167
|
+
if hasattr(engine_module, "RayTracer"):
|
|
168
|
+
cls = engine_module.RayTracer
|
|
169
|
+
if not hasattr(cls, "_original_render"):
|
|
170
|
+
cls._original_render = cls.render
|
|
171
|
+
cls.render = accelerated_render
|
fastpython/api.py
CHANGED
|
@@ -34,7 +34,7 @@ def get_cache() -> ModuleCache:
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class AcceleratedFunction:
|
|
37
|
-
"""Wrapper that holds both the original Python function and
|
|
37
|
+
"""Wrapper that holds both the original Python function and polymorphic compiled native specializations."""
|
|
38
38
|
def __init__(
|
|
39
39
|
self,
|
|
40
40
|
py_func: Callable[..., Any],
|
|
@@ -47,71 +47,73 @@ class AcceleratedFunction:
|
|
|
47
47
|
):
|
|
48
48
|
self.py_func = py_func
|
|
49
49
|
self.func_name = getattr(py_func, "__name__", "anonymous")
|
|
50
|
-
self.
|
|
50
|
+
self.base_type_hints = type_hints or getattr(py_func, "__annotations__", {})
|
|
51
51
|
self.parallel = parallel
|
|
52
52
|
self.fastmath = fastmath
|
|
53
53
|
self.verbose = verbose
|
|
54
54
|
self.source_code = source_code
|
|
55
|
-
self.
|
|
55
|
+
self.specializations: Dict[Tuple[type, ...], Callable[..., Any]] = {}
|
|
56
56
|
self.compilation_time_ms: float = 0.0
|
|
57
|
-
self.is_compiled: bool = False
|
|
58
57
|
|
|
59
|
-
if eager:
|
|
60
|
-
self.
|
|
61
|
-
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
if eager and self.base_type_hints:
|
|
59
|
+
self._compile_specialization(tuple())
|
|
60
|
+
|
|
61
|
+
def _infer_hints_from_args(self, args: tuple) -> Dict[str, str]:
|
|
62
|
+
hints = {}
|
|
63
|
+
for k, v in self.base_type_hints.items():
|
|
64
|
+
if v is int or v == "int" or v == "int64_t":
|
|
65
|
+
hints[k] = "int64_t"
|
|
66
|
+
elif v is float or v == "float" or v == "double":
|
|
67
|
+
hints[k] = "double"
|
|
68
|
+
elif v is bool or v == "bool":
|
|
69
|
+
hints[k] = "bool"
|
|
70
|
+
else:
|
|
71
|
+
hints[k] = str(v)
|
|
65
72
|
|
|
73
|
+
try:
|
|
74
|
+
sig = inspect.signature(self.py_func)
|
|
75
|
+
for param, val in zip(sig.parameters.values(), args):
|
|
76
|
+
if param.name not in hints:
|
|
77
|
+
if isinstance(val, bool):
|
|
78
|
+
hints[param.name] = "bool"
|
|
79
|
+
elif isinstance(val, int):
|
|
80
|
+
hints[param.name] = "int64_t"
|
|
81
|
+
elif isinstance(val, float):
|
|
82
|
+
hints[param.name] = "double"
|
|
83
|
+
elif hasattr(val, "__len__") and hasattr(val, "__getitem__"):
|
|
84
|
+
hints[param.name] = "double* __restrict"
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
return hints
|
|
88
|
+
|
|
89
|
+
def _compile_specialization(self, arg_types: Tuple[type, ...], sample_args: Optional[tuple] = None) -> Optional[Callable[..., Any]]:
|
|
66
90
|
toolchain = get_toolchain()
|
|
67
91
|
cache = get_cache()
|
|
68
92
|
cpu = get_cpu_info()
|
|
69
93
|
|
|
70
|
-
|
|
71
|
-
if sample_args and not self.type_hints:
|
|
72
|
-
sig = inspect.signature(self.py_func)
|
|
73
|
-
inferred = {}
|
|
74
|
-
for param, val in zip(sig.parameters.values(), sample_args):
|
|
75
|
-
if isinstance(val, int):
|
|
76
|
-
inferred[param.name] = "int64_t"
|
|
77
|
-
elif isinstance(val, float):
|
|
78
|
-
inferred[param.name] = "double"
|
|
79
|
-
elif isinstance(val, bool):
|
|
80
|
-
inferred[param.name] = "bool"
|
|
81
|
-
elif hasattr(val, "__len__") and hasattr(val, "__getitem__"):
|
|
82
|
-
inferred[param.name] = "double* __restrict"
|
|
83
|
-
self.type_hints.update(inferred)
|
|
84
|
-
|
|
94
|
+
resolved_hints = self._infer_hints_from_args(sample_args or tuple())
|
|
85
95
|
t0 = time.perf_counter()
|
|
86
96
|
|
|
87
97
|
try:
|
|
88
|
-
# Get source code
|
|
89
98
|
if self.source_code:
|
|
90
99
|
raw_source = self.source_code
|
|
91
100
|
else:
|
|
92
|
-
|
|
93
|
-
raw_source = inspect.getsource(self.py_func)
|
|
94
|
-
except Exception:
|
|
95
|
-
raw_source = ""
|
|
101
|
+
raw_source = inspect.getsource(self.py_func)
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# Compute hash from source + settings first
|
|
101
|
-
meta = f"parallel={self.parallel}_fastmath={self.fastmath}_hints={self.type_hints}"
|
|
103
|
+
type_sig_str = "_".join(str(t) for t in resolved_hints.values())
|
|
104
|
+
meta = f"parallel={self.parallel}_fastmath={self.fastmath}_sig={type_sig_str}"
|
|
102
105
|
code_hash = cache.compute_hash(raw_source, meta)
|
|
103
106
|
mod_name = f"native_{self.func_name}_{code_hash}"
|
|
104
107
|
c_path, pyd_path = cache.get_module_paths(mod_name, code_hash)
|
|
105
108
|
|
|
106
109
|
if not cache.is_cached(pyd_path):
|
|
107
110
|
if self.verbose:
|
|
108
|
-
print(f"[FastPython]
|
|
111
|
+
print(f"[FastPython.JIT] Specializing '{self.func_name}' for types ({', '.join(resolved_hints.values())}) on {cpu.summary()}...")
|
|
109
112
|
|
|
110
|
-
# Generate C source with matching module name
|
|
111
113
|
c_source = generate_c_extension_source(
|
|
112
114
|
self.py_func,
|
|
113
115
|
module_name=mod_name,
|
|
114
|
-
type_hints=
|
|
116
|
+
type_hints=resolved_hints,
|
|
115
117
|
parallel=self.parallel,
|
|
116
118
|
fastmath=self.fastmath,
|
|
117
119
|
source_code=raw_source
|
|
@@ -129,25 +131,28 @@ class AcceleratedFunction:
|
|
|
129
131
|
elapsed = (time.perf_counter() - t0) * 1000
|
|
130
132
|
self.compilation_time_ms = elapsed
|
|
131
133
|
if self.verbose:
|
|
132
|
-
print(f"[FastPython] '{self.func_name}'
|
|
134
|
+
print(f"[FastPython.JIT] '{self.func_name}' [{type_sig_str}] compiled in {elapsed:.1f}ms! Direct dispatch active.")
|
|
133
135
|
else:
|
|
134
136
|
if self.verbose:
|
|
135
|
-
print(f"[FastPython]
|
|
137
|
+
print(f"[FastPython.JIT] Instant cached dispatch for '{self.func_name}' ({type_sig_str})")
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
native_fn = cache.load_extension(mod_name, pyd_path, self.func_name)
|
|
140
|
+
self.specializations[arg_types] = native_fn
|
|
141
|
+
return native_fn
|
|
138
142
|
except Exception as e:
|
|
139
143
|
if self.verbose:
|
|
140
|
-
print(f"[FastPython]
|
|
141
|
-
self.
|
|
142
|
-
|
|
143
|
-
self.is_compiled = True
|
|
144
|
+
print(f"[FastPython.JIT] Specialization fallback for '{self.func_name}': {e}. Using pure Python.")
|
|
145
|
+
self.specializations[arg_types] = self.py_func
|
|
146
|
+
return self.py_func
|
|
144
147
|
|
|
145
148
|
def __call__(self, *args, **kwargs):
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
arg_types = tuple(type(a) for a in args)
|
|
150
|
+
target = self.specializations.get(arg_types)
|
|
151
|
+
if target is None:
|
|
152
|
+
target = self._compile_specialization(arg_types, sample_args=args)
|
|
148
153
|
|
|
149
|
-
if
|
|
150
|
-
return
|
|
154
|
+
if target is not None:
|
|
155
|
+
return target(*args, **kwargs)
|
|
151
156
|
return self.py_func(*args, **kwargs)
|
|
152
157
|
|
|
153
158
|
|