[svn] / trunk / xvidcore / src / image / ppc_asm / qpel_altivec.c Repository:
ViewVC logotype

Annotation of /trunk/xvidcore/src/image/ppc_asm/qpel_altivec.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1570 - (view) (download)

1 : edgomez 1557 /*****************************************************************************
2 :     *
3 :     * XVID MPEG-4 VIDEO CODEC
4 :     * - QPel interpolation with altivec optimization -
5 :     *
6 :     * Copyright(C) 2004 Christoph NŠgeli <chn@kbw.ch>
7 :     *
8 :     * This program is free software ; you can redistribute it and/or modify
9 :     * it under the terms of the GNU General Public License as published by
10 :     * the Free Software Foundation ; either version 2 of the License, or
11 :     * (at your option) any later version.
12 :     *
13 :     * This program is distributed in the hope that it will be useful,
14 :     * but WITHOUT ANY WARRANTY ; without even the implied warranty of
15 :     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 :     * GNU General Public License for more details.
17 :     *
18 :     * You should have received a copy of the GNU General Public License
19 :     * along with this program ; if not, write to the Free Software
20 :     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 :     *
22 : edgomez 1570 * $Id: qpel_altivec.c,v 1.2 2004-12-09 23:02:54 edgomez Exp $
23 : edgomez 1557 *
24 :     ****************************************************************************/
25 :    
26 :    
27 :    
28 :     #ifdef HAVE_ALTIVEC_H
29 :     #include <altivec.h>
30 :     #endif
31 :    
32 :     #include "../../portab.h"
33 :    
34 :     #undef DEBUG
35 :     #include <stdio.h>
36 :    
37 :     static const vector signed char FIR_Tab_16[17] = {
38 :     (vector signed char)AVV( 14, -3, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
39 :     (vector signed char)AVV( 23, 19, -6, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
40 :     (vector signed char)AVV( -7, 20, 20, -6, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
41 :     (vector signed char)AVV( 3, -6, 20, 20, -6, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
42 :     (vector signed char)AVV( -1, 3, -6, 20, 20, -6, 3, -1, 0, 0, 0, 0, 0, 0, 0, 0 ),
43 :     (vector signed char)AVV( 0, -1, 3, -6, 20, 20, -6, 3, -1, 0, 0, 0, 0, 0, 0, 0 ),
44 :     (vector signed char)AVV( 0, 0, -1, 3, -6, 20, 20, -6, 3, -1, 0, 0, 0, 0, 0, 0 ),
45 :     (vector signed char)AVV( 0, 0, 0, -1, 3, -6, 20, 20, -6, 3, -1, 0, 0, 0, 0, 0 ),
46 :     (vector signed char)AVV( 0, 0, 0, 0, -1, 3, -6, 20, 20, -6, 3, -1, 0, 0, 0, 0 ),
47 :     (vector signed char)AVV( 0, 0, 0, 0, 0, -1, 3, -6, 20, 20, -6, 3, -1, 0, 0, 0 ),
48 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, -1, 3, -6, 20, 20, -6, 3, -1, 0, 0 ),
49 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, 0, -1, 3, -6, 20, 20, -6, 3, -1, 0 ),
50 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, -6, 20, 20, -6, 3, -1 ),
51 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, -6, 20, 20, -6, 3 ),
52 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, -6, 20, 20, -7 ),
53 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, -6, 19, 23 ),
54 :     (vector signed char)AVV( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 2, -3, 14 )
55 :     };
56 :    
57 :     static const vector signed short FIR_Tab_8[9] = {
58 :     (vector signed short)AVV( 14, -3, 2, -1, 0, 0, 0, 0 ),
59 :     (vector signed short)AVV( 23, 19, -6, 3, -1, 0, 0, 0 ),
60 :     (vector signed short)AVV( -7, 20, 20, -6, 3, -1, 0, 0 ),
61 :     (vector signed short)AVV( 3, -6, 20, 20, -6, 3, -1, 0 ),
62 :     (vector signed short)AVV( -1, 3, -6, 20, 20, -6, 3, -1 ),
63 :     (vector signed short)AVV( 0, -1, 3, -6, 20, 20, -6, 3 ),
64 :     (vector signed short)AVV( 0, 0, -1, 3, -6, 20, 20, -7 ),
65 :     (vector signed short)AVV( 0, 0, 0, -1, 3, -6, 19, 23 ),
66 :     (vector signed short)AVV( 0, 0, 0, 0, -1, 2, -3, 14 )
67 :     };
68 :    
69 :     /* Processing with FIR_Tab */
70 :     #define PROCESS_FIR_16(x) \
71 :     firs = FIR_Tab_16[x];\
72 :     \
73 :     tmp = vec_splat(vec_src,(x));\
74 :     sums1 = vec_mladd( (vector signed short)vec_mergeh(ox00, tmp), vec_unpackh(firs), sums1 );\
75 :     sums2 = vec_mladd( (vector signed short)vec_mergel(ox00, tmp), vec_unpackl(firs), sums2 )
76 :    
77 :     #define PROCESS_FIR_8(x) \
78 :     firs = FIR_Tab_8[x];\
79 :     tmp = (vector signed short)vec_mergeh( ox00, vec_splat(vec_src,x) );\
80 :     sums = vec_mladd(firs,tmp,sums)
81 :    
82 :     #define NOTHING() \
83 :     /* Nothing here */
84 :    
85 :     #pragma mark -
86 :    
87 :     /* "Postprocessing" macros */
88 :    
89 :     #define AVRG_16() \
90 :     sums1 = (vector signed short)vec_mergeh(ox00,tmp);\
91 :     sums2 = (vector signed short)vec_mergel(ox00,tmp);\
92 :     sums1 = vec_add(sums1, (vector signed short)vec_mergeh(ox00,vec_src) );\
93 :     sums2 = vec_add(sums2, (vector signed short)vec_mergel(ox00,vec_src) );\
94 :     tmp = (vector unsigned char)vec_splat_u16(1);\
95 :     sums1 = vec_add(sums1, (vector signed short)tmp);\
96 :     sums2 = vec_add(sums2, (vector signed short)tmp);\
97 :     sums1 = vec_sub(sums1, vec_rnd);\
98 :     sums2 = vec_sub(sums2, vec_rnd);\
99 :     sums1 = vec_sra(sums1, (vector unsigned short)tmp);\
100 :     sums2 = vec_sra(sums2, (vector unsigned short)tmp);\
101 :     tmp = vec_packsu(sums1,sums2)
102 :    
103 :     #define AVRG_UP_16_H() \
104 :     vec_src = vec_perm(vec_ld(1,Src),vec_ld(17,Src),vec_lvsl(1,Src));\
105 :     AVRG_16()
106 :    
107 :     #define AVRG_UP_16_V() \
108 :     ((unsigned char*)&vec_src)[0] = Src[16 * BpS];\
109 :     vec_src = vec_perm(vec_src,vec_src,vec_lvsl(1,(unsigned char*)0));\
110 :     AVRG_16()
111 :    
112 :    
113 :     #define AVRG_8() \
114 :     sums = (vector signed short)vec_mergeh(ox00, st);\
115 :     sums = vec_add(sums, (vector signed short)vec_mergeh(ox00, vec_src));\
116 :     st = (vector unsigned char)vec_splat_u16(1);\
117 :     sums = vec_add(sums, (vector signed short)st);\
118 :     sums = vec_sub(sums, vec_rnd);\
119 :     sums = vec_sra(sums, (vector unsigned short)st);\
120 :     st = vec_packsu(sums,sums)
121 :    
122 :    
123 :     #define AVRG_UP_8() \
124 :     vec_src = vec_perm(vec_src, vec_src, vec_lvsl(1,(unsigned char*)0));\
125 :     AVRG_8()
126 :    
127 :     #pragma mark -
128 :    
129 :     /* Postprocessing Macros for the Pass_16 Add functions */
130 :     #define ADD_16_H()\
131 :     tmp = vec_avg(tmp, vec_perm(vec_ld(0,Dst),vec_ld(16,Dst),vec_lvsl(0,Dst)))
132 :    
133 :     #define AVRG_ADD_16_H()\
134 :     AVRG_16();\
135 :     ADD_16_H()
136 :    
137 :     #define AVRG_UP_ADD_16_H()\
138 :     AVRG_UP_16_H();\
139 :     ADD_16_H()
140 :    
141 :     #define ADD_16_V() \
142 :     for(j = 0; j < 16; j++)\
143 :     ((unsigned char*)&vec_src)[j] = Dst[j * BpS];\
144 :     tmp = vec_avg(tmp, vec_src)
145 :    
146 :     #define AVRG_ADD_16_V()\
147 :     AVRG_16();\
148 :     ADD_16_V()
149 :    
150 :     #define AVRG_UP_ADD_16_V()\
151 :     AVRG_UP_16_V();\
152 :     ADD_16_V()
153 :    
154 :     #pragma mark -
155 :    
156 :     /* Postprocessing Macros for the Pass_8 Add functions */
157 :     #define ADD_8_H()\
158 :     sums = (vector signed short)vec_mergeh(ox00, st);\
159 :     tmp = (vector signed short)vec_mergeh(ox00,vec_perm(vec_ld(0,Dst),vec_ld(16,Dst),vec_lvsl(0,Dst)));\
160 :     sums = vec_avg(sums,tmp);\
161 :     st = vec_packsu(sums,sums)
162 :    
163 :     #define AVRG_ADD_8_H()\
164 :     AVRG_8();\
165 :     ADD_8_H()
166 :    
167 :     #define AVRG_UP_ADD_8_H()\
168 :     AVRG_UP_8();\
169 :     ADD_8_H()
170 :    
171 :    
172 :     #define ADD_8_V()\
173 :     for(j = 0; j < 8; j++)\
174 :     ((short*)&tmp)[j] = (short)Dst[j * BpS];\
175 :     sums = (vector signed short)vec_mergeh(ox00,st);\
176 :     sums = vec_avg(sums,tmp);\
177 :     st = vec_packsu(sums,sums)
178 :    
179 :     #define AVRG_ADD_8_V()\
180 :     AVRG_8();\
181 :     ADD_8_V()
182 :    
183 :     #define AVRG_UP_ADD_8_V()\
184 :     AVRG_UP_8();\
185 :     ADD_8_V()
186 :    
187 :     #pragma mark -
188 :    
189 :     /* Load/Store Macros */
190 :     #define LOAD_H() \
191 :     vec_src = vec_perm(vec_ld(0,Src),vec_ld(16,Src),vec_lvsl(0,Src))
192 :    
193 :     #define LOAD_V_16() \
194 :     for(j = 0; j < 16; j++)\
195 :     ((unsigned char*)&vec_src)[j] = Src[j * BpS]
196 :    
197 :     #define LOAD_V_8() \
198 :     for(j = 0; j <= 8; j++)\
199 :     ((unsigned char*)&vec_src)[j] = Src[j * BpS]
200 :    
201 :     #define LOAD_UP_V_8() \
202 :     for(j = 0; j <= 9; j++)\
203 :     ((unsigned char*)&vec_src)[j] = Src[j * BpS]
204 :    
205 :     #define STORE_H_16() \
206 :     mask = vec_lvsr(0,Dst);\
207 :     tmp = vec_perm(tmp,tmp,mask);\
208 :     mask = vec_perm(oxFF, ox00, mask);\
209 : edgomez 1570 tmp1 = vec_sel(tmp, vec_ld(0,Dst), mask);\
210 :     vec_st(tmp1, 0, Dst);\
211 :     tmp1 = vec_sel(vec_ld(16,Dst), tmp, mask);\
212 :     vec_st(tmp1, 16, Dst)
213 : edgomez 1557
214 :     #define STORE_V_16() \
215 :     for(j = 0; j < 16; j++)\
216 :     Dst[j * BpS] = ((unsigned char*)&tmp)[j]
217 :    
218 :    
219 :     #define STORE_H_8() \
220 :     mask = vec_perm(mask_00ff, mask_00ff, vec_lvsr(0,Dst) );\
221 :     st = vec_sel(st, vec_ld(0,Dst), mask);\
222 :     vec_st(st, 0, Dst)
223 :    
224 :     #define STORE_V_8() \
225 :     for(j = 0; j < 8; j++)\
226 :     Dst[j * BpS] = ((unsigned char*)&st)[j]
227 :    
228 :    
229 :     #pragma mark -
230 :    
231 :     /* Additional variable declaration/initialization macros */
232 :    
233 :     #define VARS_H_16()\
234 : edgomez 1570 register vector unsigned char mask;\
235 :     register vector unsigned char tmp1
236 : edgomez 1557
237 :    
238 :     #define VARS_V()\
239 :     register unsigned j
240 :    
241 :    
242 :     #define VARS_H_8()\
243 :     register vector unsigned char mask_00ff;\
244 :     register vector unsigned char mask;\
245 :     mask_00ff = vec_pack(vec_splat_u16(0),vec_splat_u16(-1))
246 :    
247 :     #pragma mark -
248 :    
249 :     /* Function macros */
250 :    
251 :     #define MAKE_PASS_16(NAME, POSTPROC, ADDITIONAL_VARS, LOAD_SOURCE, STORE_DEST, NEXT_PIXEL, NEXT_LINE) \
252 :     void \
253 :     NAME(uint8_t *Dst, const uint8_t *Src, int32_t H, int32_t BpS, int32_t Rnd) \
254 :     {\
255 :     register vector signed short sums1,sums2;\
256 :     register vector unsigned char ox00;\
257 :     register vector unsigned char oxFF;\
258 :     register vector signed char firs;\
259 :     vector signed short vec_rnd;\
260 :     vector signed short s16Rnd;\
261 :     vector unsigned char vec_src;\
262 :     vector unsigned char tmp;\
263 :     \
264 :     register unsigned c;\
265 :     \
266 :     ADDITIONAL_VARS();\
267 :     \
268 :     ox00 = vec_splat_u8(0);\
269 :     oxFF = vec_splat_u8(-1);\
270 :     \
271 :     *((short*)&vec_rnd) = (short)Rnd;\
272 :     vec_rnd = vec_splat(vec_rnd,0);\
273 :     s16Rnd = vec_add(vec_splat_s16(8),vec_splat_s16(8));\
274 :     s16Rnd = vec_sub(s16Rnd, vec_rnd);\
275 :     \
276 :     c = ((1 << 24) | (16 << 16) | BpS);\
277 :     \
278 :     while(H-- > 0) {\
279 :     \
280 :     vec_dst(Src, c, 2);\
281 :     \
282 :     sums1 = s16Rnd;\
283 :     sums2 = s16Rnd;\
284 :     \
285 :     LOAD_SOURCE();\
286 :     \
287 :     PROCESS_FIR_16(0);\
288 :     PROCESS_FIR_16(1);\
289 :     PROCESS_FIR_16(2);\
290 :     PROCESS_FIR_16(3);\
291 :     \
292 :     PROCESS_FIR_16(4);\
293 :     PROCESS_FIR_16(5);\
294 :     PROCESS_FIR_16(6);\
295 :     PROCESS_FIR_16(7);\
296 :     \
297 :     PROCESS_FIR_16(8);\
298 :     PROCESS_FIR_16(9);\
299 :     PROCESS_FIR_16(10);\
300 :     PROCESS_FIR_16(11);\
301 :     \
302 :     PROCESS_FIR_16(12);\
303 :     PROCESS_FIR_16(13);\
304 :     PROCESS_FIR_16(14);\
305 :     PROCESS_FIR_16(15);\
306 :     \
307 :     firs = FIR_Tab_16[16];\
308 :     *((uint8_t*)&tmp) = Src[16*NEXT_PIXEL];\
309 :     tmp = vec_splat(tmp,0);\
310 :     \
311 :     sums1 = vec_mladd( (vector signed short)vec_mergeh(ox00,tmp),vec_unpackh(firs),sums1 );\
312 :     sums2 = vec_mladd( (vector signed short)vec_mergel(ox00,tmp),vec_unpackl(firs),sums2 );\
313 :     \
314 :     tmp = (vector unsigned char)vec_splat_u16(5);\
315 :     sums1 = vec_sra(sums1,(vector unsigned short)tmp);\
316 :     sums2 = vec_sra(sums2,(vector unsigned short)tmp);\
317 :     tmp = vec_packsu(sums1,sums2);\
318 :     \
319 :     POSTPROC();\
320 :     \
321 :     STORE_DEST();\
322 :     \
323 :     Src += NEXT_LINE;\
324 :     Dst += NEXT_LINE;\
325 :     }\
326 :     vec_dss(2);\
327 :     }
328 :    
329 :     #define MAKE_PASS_8(NAME,POSTPROC,ADDITIONAL_VARS, LOAD_SOURCE, STORE_DEST, INC) \
330 :     void \
331 :     NAME(uint8_t *Dst, const uint8_t *Src, int32_t H, int32_t BpS, int32_t Rnd)\
332 :     {\
333 :     register vector signed short sums;\
334 :     register vector signed short firs;\
335 :     register vector unsigned char ox00;\
336 :     vector signed short tmp;\
337 :     vector signed short vec_rnd;\
338 :     vector signed short vec_rnd16;\
339 :     vector unsigned char vec_src;\
340 :     vector unsigned char st;\
341 :     \
342 :     ADDITIONAL_VARS();\
343 :     \
344 :     ox00 = vec_splat_u8(0);\
345 :     \
346 :     *((short*)&vec_rnd) = (short)Rnd;\
347 :     vec_rnd = vec_splat(vec_rnd,0);\
348 :     vec_rnd16 = vec_sub( vec_add(vec_splat_s16(8),vec_splat_s16(8)), vec_rnd );\
349 :     \
350 :     while(H-- > 0) {\
351 :     \
352 :     sums = vec_rnd16;\
353 :     LOAD_SOURCE();\
354 :     \
355 :     PROCESS_FIR_8(0);\
356 :     PROCESS_FIR_8(1);\
357 :     PROCESS_FIR_8(2);\
358 :     PROCESS_FIR_8(3);\
359 :     \
360 :     PROCESS_FIR_8(4);\
361 :     PROCESS_FIR_8(5);\
362 :     PROCESS_FIR_8(6);\
363 :     PROCESS_FIR_8(7);\
364 :     \
365 :     PROCESS_FIR_8(8);\
366 :     \
367 :     sums = vec_sra(sums, vec_splat_u16(5));\
368 :     st = vec_packsu(sums,sums);\
369 :     \
370 :     POSTPROC();\
371 :     \
372 :     STORE_DEST();\
373 :     \
374 :     Src += INC;\
375 :     Dst += INC;\
376 :     }\
377 :     }
378 :    
379 :    
380 :     /* Create the actual Functions
381 :     ***************************************/
382 :    
383 :     /* These functions assume no alignment */
384 :     MAKE_PASS_16(H_Pass_16_Altivec_C, NOTHING, VARS_H_16, LOAD_H, STORE_H_16, 1, BpS)
385 :     MAKE_PASS_16(H_Pass_Avrg_16_Altivec_C, AVRG_16, VARS_H_16, LOAD_H, STORE_H_16, 1, BpS)
386 :     MAKE_PASS_16(H_Pass_Avrg_Up_16_Altivec_C, AVRG_UP_16_H, VARS_H_16, LOAD_H, STORE_H_16, 1, BpS)
387 :    
388 :     MAKE_PASS_16(V_Pass_16_Altivec_C, NOTHING, VARS_V, LOAD_V_16, STORE_V_16, BpS, 1)
389 :     MAKE_PASS_16(V_Pass_Avrg_16_Altivec_C, AVRG_16, VARS_V, LOAD_V_16, STORE_V_16, BpS, 1)
390 :     MAKE_PASS_16(V_Pass_Avrg_Up_16_Altivec_C, AVRG_UP_16_V, VARS_V, LOAD_V_16, STORE_V_16, BpS, 1)
391 :    
392 :    
393 :     /* These functions assume:
394 :     * Dst: 8 Byte aligned
395 :     * BpS: Multiple of 8
396 :     */
397 :     MAKE_PASS_8(H_Pass_8_Altivec_C, NOTHING, VARS_H_8, LOAD_H, STORE_H_8, BpS)
398 :     MAKE_PASS_8(H_Pass_Avrg_8_Altivec_C, AVRG_8, VARS_H_8, LOAD_H, STORE_H_8, BpS)
399 :     MAKE_PASS_8(H_Pass_Avrg_Up_8_Altivec_C, AVRG_UP_8, VARS_H_8, LOAD_H, STORE_H_8, BpS)
400 :    
401 :     /* These functions assume no alignment */
402 :     MAKE_PASS_8(V_Pass_8_Altivec_C, NOTHING, VARS_V, LOAD_V_8, STORE_V_8, 1)
403 :     MAKE_PASS_8(V_Pass_Avrg_8_Altivec_C, AVRG_8, VARS_V, LOAD_V_8, STORE_V_8, 1)
404 :     MAKE_PASS_8(V_Pass_Avrg_Up_8_Altivec_C, AVRG_UP_8, VARS_V, LOAD_UP_V_8, STORE_V_8, 1)
405 :    
406 :    
407 :     /* These functions assume no alignment */
408 :     MAKE_PASS_16(H_Pass_16_Add_Altivec_C, ADD_16_H, VARS_H_16, LOAD_H, STORE_H_16, 1, BpS)
409 :     MAKE_PASS_16(H_Pass_Avrg_16_Add_Altivec_C, AVRG_ADD_16_H, VARS_H_16, LOAD_H, STORE_H_16, 1, BpS)
410 :     MAKE_PASS_16(H_Pass_Avrg_Up_16_Add_Altivec_C, AVRG_UP_ADD_16_H, VARS_H_16, LOAD_H, STORE_H_16, 1, BpS)
411 :    
412 :     MAKE_PASS_16(V_Pass_16_Add_Altivec_C, ADD_16_V, VARS_V, LOAD_V_16, STORE_V_16, BpS, 1)
413 :     MAKE_PASS_16(V_Pass_Avrg_16_Add_Altivec_C, AVRG_ADD_16_V, VARS_V, LOAD_V_16, STORE_V_16, BpS, 1)
414 :     MAKE_PASS_16(V_Pass_Avrg_Up_16_Add_Altivec_C, AVRG_UP_ADD_16_V, VARS_V, LOAD_V_16, STORE_V_16, BpS, 1)
415 :    
416 :    
417 :     /* These functions assume:
418 :     * Dst: 8 Byte aligned
419 :     * BpS: Multiple of 8
420 :     */
421 :     MAKE_PASS_8(H_Pass_8_Add_Altivec_C, ADD_8_H, VARS_H_8, LOAD_H, STORE_H_8, BpS)
422 :     MAKE_PASS_8(H_Pass_Avrg_8_Add_Altivec_C, AVRG_ADD_8_H, VARS_H_8, LOAD_H, STORE_H_8, BpS)
423 :     MAKE_PASS_8(H_Pass_Avrg_Up_8_Add_Altivec_C, AVRG_UP_ADD_8_H, VARS_H_8, LOAD_H, STORE_H_8, BpS)
424 :    
425 :    
426 :     /* These functions assume no alignment */
427 :     MAKE_PASS_8(V_Pass_8_Add_Altivec_C, ADD_8_V, VARS_V, LOAD_V_8, STORE_V_8, 1)
428 :     MAKE_PASS_8(V_Pass_Avrg_8_Add_Altivec_C, AVRG_ADD_8_V, VARS_V, LOAD_V_8, STORE_V_8, 1)
429 :     MAKE_PASS_8(V_Pass_Avrg_Up_8_Add_Altivec_C, AVRG_UP_ADD_8_V, VARS_V, LOAD_UP_V_8, STORE_V_8, 1)

No admin address has been configured
ViewVC Help
Powered by ViewVC 1.0.4